From 452af27b78895ec7b1db52d6bab90678e853a64f Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Fri, 26 Jul 2024 14:21:39 -0400 Subject: [PATCH 01/20] data dictionary table with minor features replicated in primeng --- angular.json | 4 +- package-lock.json | 16 ++ package.json | 5 +- src/app/app-routing.module.ts | 8 + src/app/app.module.ts | 13 +- .../components/sidenav/sidenav.component.html | 5 + .../data-dictionary-primeng.component.css | 0 .../data-dictionary-primeng.component.html | 70 ++++++++ .../data-dictionary-primeng.component.spec.ts | 25 +++ .../data-dictionary-primeng.component.ts | 164 ++++++++++++++++++ 10 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.css create mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html create mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts create mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts diff --git a/angular.json b/angular.json index d05d884b..c9fe1fba 100644 --- a/angular.json +++ b/angular.json @@ -39,7 +39,9 @@ "node_modules/bootstrap-table/dist/bootstrap-table.min.css", "node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css", "node_modules/@ng-select/ng-select/themes/default.theme.css", - "node_modules/@uswds/uswds/dist/css/uswds.css" + "node_modules/@uswds/uswds/dist/css/uswds.css", + "node_modules/primeng/resources/themes/lara-light-blue/theme.css", + "node_modules/primeng/resources/primeng.min.css" ], "scripts": [ "node_modules/jquery/dist/jquery.min.js", diff --git a/package-lock.json b/package-lock.json index 773f8305..39456faf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "passport-jwt": "^4.0.0", "path": "^0.12.7", "pdfjs-dist": "^2.16.105", + "primeng": "^17.18.6", "readline": "^1.3.0", "rxjs": "~6.5.4", "tableexport.jquery.plugin": "1.20.1", @@ -19406,6 +19407,21 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/primeng": { + "version": "17.18.6", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.6.tgz", + "integrity": "sha512-WnsUWUxNkeqiJf5yrdT/TTzVyCP6R7Z60sr87chh3D7BNuNqh2R5Yd6GI86QVRhE172yt3AjjSIWfqRhzyul5w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "rxjs": "^6.0.0 || ^7.8.1", + "zone.js": "~0.14.0" + } + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", diff --git a/package.json b/package.json index f6a99295..79b8747e 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "ldapjs": "^2.3.1", "make-fetch-happen": "^10.1.2", "mysql2": "^3.9.7", + "ng-sidebar-v3": "^18.0.0", "ng2-pdf-viewer": "^10.2.2", "ngx-cookie-service": "^18.0.0", "node-cron": "^2.0.3", @@ -80,6 +81,7 @@ "passport-jwt": "^4.0.0", "path": "^0.12.7", "pdfjs-dist": "^2.16.105", + "primeng": "^17.18.6", "readline": "^1.3.0", "rxjs": "~6.5.4", "tableexport.jquery.plugin": "1.20.1", @@ -88,8 +90,7 @@ "webpack-dev-server": "^5.0.4", "xmlhttprequest": "^1.8.0", "xmlhttprequest-ssl": "^1.6.3", - "zone.js": "^0.14.6", - "ng-sidebar-v3": "^18.0.0" + "zone.js": "^0.14.6" }, "devDependencies": { "@angular-devkit/build-angular": "^18.0.7", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 9bb0b352..f2ff9224 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -44,6 +44,7 @@ import { Title } from '@angular/platform-browser'; import { FormsComponent } from './views/main/forms-glossary/forms/forms.component'; import { GlossaryComponent } from './views/main/forms-glossary/glossary/glossary.component'; import { DataDictionaryComponent } from './views/main/data-dictionary/data-dictionary.component'; +import { DataDictionaryPrimeNGComponent } from './views/main/data-dictionary-primeng/data-dictionary-primeng.component'; const routes: Routes = [ { path: '', component: HomeComponent, title: 'Home' }, @@ -204,6 +205,13 @@ const routes: Routes = [ title: 'Data Dictionary', }, + { + path: 'data_dictionary_primeng', + component: DataDictionaryPrimeNGComponent, + title: 'Data Dictionary - PrimeNG POC', + }, + + { // Catch-all Redirect to Home path: '**', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8f783ec9..69a4943b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -76,6 +76,12 @@ import { Globals } from './common/globals'; import { YesNoPipe } from "./pipes/yesno.pipe"; import { SkipFocusPiechartDirective } from '@common/skip-focus-piechart.directive'; import { BannerComponent } from './components/banner/banner.component'; +import { DataDictionaryPrimeNGComponent } from './views/main/data-dictionary-primeng/data-dictionary-primeng.component'; + +// PrimeNG Modules +import { TableModule } from 'primeng/table'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { ButtonModule } from 'primeng/button'; @NgModule({ declarations: [ AppComponent, @@ -122,6 +128,7 @@ import { BannerComponent } from './components/banner/banner.component'; YesNoPipe, SkipFocusPiechartDirective, BannerComponent, + DataDictionaryPrimeNGComponent ], bootstrap: [AppComponent], imports: [AppRoutingModule, BrowserAnimationsModule, @@ -131,7 +138,11 @@ import { BannerComponent } from './components/banner/banner.component'; NgxChartsModule, PdfViewerModule, ReactiveFormsModule, - SidebarModule.forRoot()], providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) + SidebarModule.forRoot(), + TableModule, + MultiSelectModule, + ButtonModule + ], providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) export class AppModule { constructor() {} } diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index a716c5c9..c0dc7d94 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -348,6 +348,11 @@ class="bg-dark list-group-item list-group-item-action" > Data Dictionary + + Data Dictionary - PrimeNG POC +
+

+ + + Data Dictionary + +

+ +
+ More Help + +
+ +
+
+

Data Dictionary

+
+ + + + + + +
+ + + + +
+ {{ col.header }} + + + +
+ + +
+ + + +
+ {{ rowData[col.field] }} +
+ + +
+
+
+
+ \ No newline at end of file diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts new file mode 100644 index 00000000..3d9e7b26 --- /dev/null +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DataDictionaryPrimeNGComponent } from './data-dictionary-primeng.component'; + +describe('DataDictionaryPrimeNGComponent', () => { + let component: DataDictionaryPrimeNGComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ DataDictionaryPrimeNGComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DataDictionaryPrimeNGComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts new file mode 100644 index 00000000..b54797f3 --- /dev/null +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts @@ -0,0 +1,164 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { DataDictionary } from '@api/models/data-dictionary.model'; +import { ApiService } from '@services/apis/api.service'; + +import { SharedService } from '@services/shared/shared.service'; +import { TableService } from '@services/tables/table.service'; +import { MultiSelect, MultiSelectChangeEvent } from 'primeng/multiselect'; + +// Declare jQuery symbol +declare var $: any; + +@Component({ + selector: 'data-dictionary-primeng', + templateUrl: './data-dictionary-primeng.component.html', + styleUrls: ['./data-dictionary-primeng.component.css'] +}) +export class DataDictionaryPrimeNGComponent implements OnInit { + + visibleColumns: any[] = []; + isPaginated: boolean = true; + + testData: DataDictionary[] = []; + + testCols = [ + { + field: 'Term', + header: 'Term', + isSortable: true, + showColumn: true + }, + { + field: 'TermDefinition', + header: 'Definition', + isSortable: false, + showColumn: true + }, + { + field: 'DefinitionSource', + header: 'Definition Source', + isSortable: true, + showColumn: true + }, + { + field: 'DefinitionSourceLink', + header: 'Definition Source Link', + isSortable: false, + showColumn: false + }, + { + field: 'DataSource', + header: 'Data Source', + isSortable: true, + showColumn: true + }, + { + field: 'DataSourceLink', + header: 'Data Source Link', + isSortable: false, + showColumn: false + } +]; + + row: Object = {}; + + constructor( + private sharedService: SharedService, + private tableService: TableService, + private apiService: ApiService) {} + + // Data Dictionary Table Options + ddTableOptions: {} = this.tableService.createTableOptions({ + advancedSearch: false, + idTable: null, + classes: "table-hover table-dark", + showColumns: false, + showExport: true, + exportFileName: 'GEAR_Data_Dictionary', + headerStyle: null, + pagination: true, + search: true, + sortName: 'Term', + sortOrder: 'asc', + showToggle: true + }); + + // Data Dictionary Table Columns + ddColumnDefs: any[] = [{ + field: 'ReportName', + title: 'ReportName', + sortable: true, + visible: false + }, + { + field: 'Term', + title: 'Term', + sortable: true + }, + { + field: 'TermDefinition', + title: 'Definition' + }, + { + field: 'DefinitionSource', + title: 'Definition Source', + sortable: true + }, + { + field: 'DefinitionSourceLink', + title: 'Definition Source Link', + formatter: this.sharedService.linksFormatter + }, + { + field: 'DataSource', + title: 'Data Source', + sortable: true + }, + { + field: 'DataSourceLink', + title: 'Data Source Link', + formatter: this.sharedService.linksFormatter + } +]; + + ngOnInit(): void { + this.apiService.getEntireDataDictionary().subscribe(defs => { + this.testData = defs; + $('#dataDictionaryTable').bootstrapTable($.extend(this.ddTableOptions, { + columns: this.ddColumnDefs, + data: defs, + })); + }); + + // Enable popovers + $(function () { + $('[data-toggle="popover"]').popover() + }) + + const self = this; + $(document).ready(function() { + //Enable table sticky header + self.sharedService.enableStickyHeader("dataDictionaryTable"); + }); + + this.testCols.map(c => { + if(c.showColumn) { + this.visibleColumns.push(c); + } + }) + } + + toggleVisible(e: any) { + console.log(e); + console.log(this.visibleColumns); + this.testCols.map(c => { + if(c.field === e.originalEvent.option.field) { + c.showColumn = e.originalEvent.selected; + } + }); + } + + togglePagination() { + this.isPaginated = !this.isPaginated; + } +} From c797dc6b36c0a7e639c79aa1a1a327ded442694b Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Tue, 30 Jul 2024 11:35:11 -0400 Subject: [PATCH 02/20] updated dd-poc page to use the new table comp instead of it being hardcoded in --- src/app/app.module.ts | 12 ++- src/app/components/table/table.component.css | 0 src/app/components/table/table.component.html | 66 +++++++++++++ .../components/table/table.component.spec.ts | 21 +++++ src/app/components/table/table.component.ts | 61 ++++++++++++ .../data-dictionary-primeng.component.html | 42 +-------- .../data-dictionary-primeng.component.ts | 92 +------------------ 7 files changed, 163 insertions(+), 131 deletions(-) create mode 100644 src/app/components/table/table.component.css create mode 100644 src/app/components/table/table.component.html create mode 100644 src/app/components/table/table.component.spec.ts create mode 100644 src/app/components/table/table.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 69a4943b..729ac804 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -82,6 +82,10 @@ import { DataDictionaryPrimeNGComponent } from './views/main/data-dictionary-pri import { TableModule } from 'primeng/table'; import { MultiSelectModule } from 'primeng/multiselect'; import { ButtonModule } from 'primeng/button'; +import { TableComponent } from './components/table/table.component'; +import { IconFieldModule } from 'primeng/iconfield'; +import { InputIconModule } from 'primeng/inputicon'; +import { InputTextModule } from 'primeng/inputtext'; @NgModule({ declarations: [ AppComponent, @@ -128,7 +132,8 @@ import { ButtonModule } from 'primeng/button'; YesNoPipe, SkipFocusPiechartDirective, BannerComponent, - DataDictionaryPrimeNGComponent + DataDictionaryPrimeNGComponent, + TableComponent ], bootstrap: [AppComponent], imports: [AppRoutingModule, BrowserAnimationsModule, @@ -141,7 +146,10 @@ import { ButtonModule } from 'primeng/button'; SidebarModule.forRoot(), TableModule, MultiSelectModule, - ButtonModule + ButtonModule, + IconFieldModule, + InputIconModule, + InputTextModule ], providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) export class AppModule { constructor() {} diff --git a/src/app/components/table/table.component.css b/src/app/components/table/table.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html new file mode 100644 index 00000000..95d4c2cb --- /dev/null +++ b/src/app/components/table/table.component.html @@ -0,0 +1,66 @@ + + + + +
+ + + + + + + + + + + +
+
+ + + +
+ {{ col.header }} + + + +
+ + + + + + + + +
+ + + +
+ {{ rowData[col.field] }} + {{ rowData[col.field] }} +
+ + +
+
\ No newline at end of file diff --git a/src/app/components/table/table.component.spec.ts b/src/app/components/table/table.component.spec.ts new file mode 100644 index 00000000..ac9e3a53 --- /dev/null +++ b/src/app/components/table/table.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableComponent } from './table.component'; + +describe('TableComponent', () => { + let component: TableComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TableComponent] + }); + fixture = TestBed.createComponent(TableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts new file mode 100644 index 00000000..80b65a22 --- /dev/null +++ b/src/app/components/table/table.component.ts @@ -0,0 +1,61 @@ +import { Component, Input, OnInit } from '@angular/core'; + +interface ExportColumn { + title: string; + dataKey: string; +} + +@Component({ + selector: 'app-table', + templateUrl: './table.component.html', + styleUrls: ['./table.component.css'] +}) + +export class TableComponent implements OnInit { + + @Input() tableCols: any[] = []; + @Input() tableData: any[] = []; + @Input() filterFields: any[] = []; + + visibleColumns: any[] = []; + isPaginated: boolean = true; + exportColumns!: ExportColumn[]; + + constructor() { } + + ngOnInit(): void { + this.exportColumns = this.tableCols.map((col) => ({ title: col.header, dataKey: col.field })); + + this.tableCols.map(c => { + if(c.showColumn) { + this.visibleColumns.push(c); + } + }) + } + + toggleVisible(e: any) { + this.tableCols.map(c => { + if(c.field === e.originalEvent.option.field) { + c.showColumn = e.originalEvent.selected; + } + }); + } + + togglePagination() { + this.isPaginated = !this.isPaginated; + } + + getExportFilename(reportName: string) { + let today = new Date(); + let year = today.getFullYear(); + let month = today.toLocaleString('default', { month: 'long' }); + let day = today.getDate(); + let hour = today.getHours(); + let mins = today.getMinutes(); + + let formattedDate = `${month}_${day}_${year}-${hour}_${mins}`; + + return `GEAR_${reportName}-${formattedDate}`; + } + +} diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html index 48c608f4..6438a618 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html @@ -24,47 +24,7 @@

-

Data Dictionary

-
- - - - - - -
- - - - -
- {{ col.header }} - - - -
- - -
- - - -
- {{ rowData[col.field] }} -
- - -
-
+
\ No newline at end of file diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts index b54797f3..76693579 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts @@ -4,7 +4,6 @@ import { ApiService } from '@services/apis/api.service'; import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; -import { MultiSelect, MultiSelectChangeEvent } from 'primeng/multiselect'; // Declare jQuery symbol declare var $: any; @@ -16,11 +15,10 @@ declare var $: any; }) export class DataDictionaryPrimeNGComponent implements OnInit { - visibleColumns: any[] = []; - isPaginated: boolean = true; - testData: DataDictionary[] = []; + testFilterFields: any[] = ['Term', 'TermDefinition', 'DefinitionSource']; + testCols = [ { field: 'Term', @@ -44,7 +42,8 @@ export class DataDictionaryPrimeNGComponent implements OnInit { field: 'DefinitionSourceLink', header: 'Definition Source Link', isSortable: false, - showColumn: false + showColumn: false, + isLink: true }, { field: 'DataSource', @@ -67,67 +66,9 @@ export class DataDictionaryPrimeNGComponent implements OnInit { private tableService: TableService, private apiService: ApiService) {} - // Data Dictionary Table Options - ddTableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: false, - idTable: null, - classes: "table-hover table-dark", - showColumns: false, - showExport: true, - exportFileName: 'GEAR_Data_Dictionary', - headerStyle: null, - pagination: true, - search: true, - sortName: 'Term', - sortOrder: 'asc', - showToggle: true - }); - - // Data Dictionary Table Columns - ddColumnDefs: any[] = [{ - field: 'ReportName', - title: 'ReportName', - sortable: true, - visible: false - }, - { - field: 'Term', - title: 'Term', - sortable: true - }, - { - field: 'TermDefinition', - title: 'Definition' - }, - { - field: 'DefinitionSource', - title: 'Definition Source', - sortable: true - }, - { - field: 'DefinitionSourceLink', - title: 'Definition Source Link', - formatter: this.sharedService.linksFormatter - }, - { - field: 'DataSource', - title: 'Data Source', - sortable: true - }, - { - field: 'DataSourceLink', - title: 'Data Source Link', - formatter: this.sharedService.linksFormatter - } -]; - ngOnInit(): void { this.apiService.getEntireDataDictionary().subscribe(defs => { this.testData = defs; - $('#dataDictionaryTable').bootstrapTable($.extend(this.ddTableOptions, { - columns: this.ddColumnDefs, - data: defs, - })); }); // Enable popovers @@ -135,30 +76,5 @@ export class DataDictionaryPrimeNGComponent implements OnInit { $('[data-toggle="popover"]').popover() }) - const self = this; - $(document).ready(function() { - //Enable table sticky header - self.sharedService.enableStickyHeader("dataDictionaryTable"); - }); - - this.testCols.map(c => { - if(c.showColumn) { - this.visibleColumns.push(c); - } - }) - } - - toggleVisible(e: any) { - console.log(e); - console.log(this.visibleColumns); - this.testCols.map(c => { - if(c.field === e.originalEvent.option.field) { - c.showColumn = e.originalEvent.selected; - } - }); - } - - togglePagination() { - this.isPaginated = !this.isPaginated; } } From 7ad7dd2984ba135f947cec638d0d49768ef9005f Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Tue, 30 Jul 2024 16:30:48 -0400 Subject: [PATCH 03/20] added ability to create dynamic filter buttons --- src/app/components/table/table.component.html | 11 ++++- ...ble.component.css => table.component.scss} | 0 src/app/components/table/table.component.ts | 27 +++++++++++- .../data-dictionary-primeng.component.html | 8 ++-- .../data-dictionary-primeng.component.ts | 42 ++++++++++++++++++- 5 files changed, 79 insertions(+), 9 deletions(-) rename src/app/components/table/{table.component.css => table.component.scss} (100%) diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 95d4c2cb..73e2285f 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -1,3 +1,10 @@ + + {{filter}} + + +Reset + + @@ -63,4 +72,4 @@ - \ No newline at end of file + diff --git a/src/app/components/table/table.component.css b/src/app/components/table/table.component.scss similarity index 100% rename from src/app/components/table/table.component.css rename to src/app/components/table/table.component.scss diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 80b65a22..8db1816f 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -1,4 +1,5 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Table } from 'primeng/table'; interface ExportColumn { title: string; @@ -8,7 +9,7 @@ interface ExportColumn { @Component({ selector: 'app-table', templateUrl: './table.component.html', - styleUrls: ['./table.component.css'] + styleUrls: ['./table.component.scss'] }) export class TableComponent implements OnInit { @@ -16,10 +17,14 @@ export class TableComponent implements OnInit { @Input() tableCols: any[] = []; @Input() tableData: any[] = []; @Input() filterFields: any[] = []; + @Input() buttonFilters: any[] = []; + + @ViewChild(Table) private dt: Table; visibleColumns: any[] = []; isPaginated: boolean = true; exportColumns!: ExportColumn[]; + currentButtonFilter: string = ''; constructor() { } @@ -58,4 +63,22 @@ export class TableComponent implements OnInit { return `GEAR_${reportName}-${formattedDate}`; } + onButtonFilter(value: string) { + this.dt.filterGlobal(value, 'contains'); + this.currentButtonFilter = value; + } + + onButtonFilterClear() { + this.dt.reset(); + this.currentButtonFilter = ''; + } + + applyFilteredStyle(filter: string) { + if(this.currentButtonFilter === filter) { + return 'filtered'; + } + + return ''; + } + } diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html index 6438a618..f6291f8c 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html @@ -22,9 +22,7 @@

-
-
- -
-
+ + + \ No newline at end of file diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts index 76693579..ca6da574 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts @@ -17,9 +17,17 @@ export class DataDictionaryPrimeNGComponent implements OnInit { testData: DataDictionary[] = []; - testFilterFields: any[] = ['Term', 'TermDefinition', 'DefinitionSource']; + testFilterFields: any[] = ['ReportName', 'Term', 'TermDefinition', 'DefinitionSource']; + + buttonFilters: any[] = []; testCols = [ + { + field: 'ReportName', + header: 'Report Name', + isSortable: true, + showColumn: true + }, { field: 'Term', header: 'Term', @@ -69,12 +77,44 @@ export class DataDictionaryPrimeNGComponent implements OnInit { ngOnInit(): void { this.apiService.getEntireDataDictionary().subscribe(defs => { this.testData = defs; + + const testEntry: DataDictionary = { + ReportName: 'IT Investments', + Term: 'Investment UII', + TermDefinition: 'A unique investment identifier', + DefinitionSource: '', + DefinitionSourceLink: '', + DataSource: 'Folio', + DataSourceLink: '' + } + + this.testData.push(testEntry); + + this.createButtonFilters(this.testData, 'ReportName'); }); // Enable popovers $(function () { $('[data-toggle="popover"]').popover() }) + } + + // TODO: Make this more generic and move it into a service + // TODO: Create arrays based on passed in property names + /* + * General Idea: the user passes in what property names they want to make filters of + * then we use those here to create seperate arrays of all unique values + * pass those arrays through a filter to remove any empty strings + * then push them all into a parent array that gets sent into the table component + * this lets us do groupings without having to pass in a bunch of "loose" data + * */ + createButtonFilters(data: any, propName: string) { + const uniqueReportNames = [...new Set(data.map(item => item.ReportName))]; + let uniqueDefinitionSource = [...new Set(data.map(item => item.DefinitionSource))] as DataDictionary[]; + uniqueDefinitionSource = uniqueDefinitionSource.filter(u => u); + this.buttonFilters.push(uniqueReportNames); + this.buttonFilters.push(uniqueDefinitionSource); + console.log(this.buttonFilters) } } From 8980905a5bbe4a2dc850f9618c4fdfbca3f6ec8d Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Wed, 14 Aug 2024 07:53:53 -0400 Subject: [PATCH 04/20] testing out possible sticky header solutions --- src/app/components/table/table.component.html | 2 +- src/app/components/table/table.component.ts | 10 ++++++++-- .../data-dictionary-primeng.component.ts | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 73e2285f..0d50069d 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -13,7 +13,7 @@ [paginator]="isPaginated" [globalFilterFields]="filterFields" [scrollable]="true" -scrollHeight="flex" +[scrollHeight]="screenHeight" [exportHeader]="'customExportHeader'" [exportFilename]="getExportFilename('Data_Dictionary')" > diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 8db1816f..2986eeff 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -25,8 +25,14 @@ export class TableComponent implements OnInit { isPaginated: boolean = true; exportColumns!: ExportColumn[]; currentButtonFilter: string = ''; - - constructor() { } + screenHeight: string = ''; + + constructor() { + this.screenHeight = `${(window.screen.height - 600).toString()}px`; + const element = document.getElementById('HTML element'); + const rect = element.getBoundingClientRect(); + console.log(rect.height); + } ngOnInit(): void { this.exportColumns = this.tableCols.map((col) => ({ title: col.header, dataKey: col.field })); diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts index ca6da574..f0fb1b06 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts @@ -110,11 +110,11 @@ export class DataDictionaryPrimeNGComponent implements OnInit { * */ createButtonFilters(data: any, propName: string) { const uniqueReportNames = [...new Set(data.map(item => item.ReportName))]; - let uniqueDefinitionSource = [...new Set(data.map(item => item.DefinitionSource))] as DataDictionary[]; - uniqueDefinitionSource = uniqueDefinitionSource.filter(u => u); + // let uniqueDefinitionSource = [...new Set(data.map(item => item.DefinitionSource))] as DataDictionary[]; + // uniqueDefinitionSource = uniqueDefinitionSource.filter(u => u); this.buttonFilters.push(uniqueReportNames); - this.buttonFilters.push(uniqueDefinitionSource); + // this.buttonFilters.push(uniqueDefinitionSource); console.log(this.buttonFilters) } } From ee9fddd779566b91c7539bdcb8f17355968e04d8 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Tue, 10 Sep 2024 12:13:45 -0400 Subject: [PATCH 05/20] update to table height --- src/app/components/table/table.component.ts | 5 +---- .../data-dictionary-primeng.component.html | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 2986eeff..c4015b10 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -28,10 +28,7 @@ export class TableComponent implements OnInit { screenHeight: string = ''; constructor() { - this.screenHeight = `${(window.screen.height - 600).toString()}px`; - const element = document.getElementById('HTML element'); - const rect = element.getBoundingClientRect(); - console.log(rect.height); + this.screenHeight = `${(window.screen.height - 700).toString()}px`; } ngOnInit(): void { diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html index f6291f8c..dd2ec416 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html @@ -22,7 +22,7 @@

- +
- +
\ No newline at end of file From 8e3a4f7c32640559871c8f0db4f581b94d773dd3 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Fri, 13 Sep 2024 16:43:03 -0400 Subject: [PATCH 06/20] screenHeight is now based on window innerHEight --- src/app/components/table/table.component.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index c4015b10..1c1690bf 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, HostListener, Input, OnInit, ViewChild } from '@angular/core'; import { Table } from 'primeng/table'; interface ExportColumn { @@ -21,6 +21,12 @@ export class TableComponent implements OnInit { @ViewChild(Table) private dt: Table; + @HostListener('window:resize', ['$event']) + onResize(event) { + console.log(event.srcElement.innerHeight); + this.screenHeight = `${(event.srcElement.innerHeight - 500)}px`; + } + visibleColumns: any[] = []; isPaginated: boolean = true; exportColumns!: ExportColumn[]; @@ -28,7 +34,7 @@ export class TableComponent implements OnInit { screenHeight: string = ''; constructor() { - this.screenHeight = `${(window.screen.height - 700).toString()}px`; + this.screenHeight = `${(window.innerHeight - 500)}px`; } ngOnInit(): void { From 01e9bb6c41a21da95bd3f0bf74050d4f50e430d0 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Tue, 24 Sep 2024 14:30:49 -0400 Subject: [PATCH 07/20] add identifier-component and replace sidebar --- angular.json | 4 +- package-lock.json | 16 +++ package.json | 1 + src/app/app.component.css | 28 ---- src/app/app.component.html | 89 ++++++++++++- src/app/app.component.scss | 117 +++++++++++++++++ src/app/app.component.ts | 11 +- src/app/app.module.ts | 15 ++- .../identifier/identifier.component.css | 3 + .../identifier/identifier.component.html | 103 +++++++++++++++ .../identifier/identifier.component.spec.ts | 21 +++ .../identifier/identifier.component.ts | 12 ++ .../components/sidebar/sidebar.component.html | 124 ++++++++++++++++++ .../components/sidebar/sidebar.component.scss | 124 ++++++++++++++++++ .../sidebar/sidebar.component.spec.ts | 21 +++ .../components/sidebar/sidebar.component.ts | 21 +++ .../components/sidenav/sidenav.component.css | 6 +- .../components/sidenav/sidenav.component.html | 56 ++++---- src/app/services/shared/shared.service.ts | 7 + 19 files changed, 712 insertions(+), 67 deletions(-) delete mode 100644 src/app/app.component.css create mode 100644 src/app/app.component.scss create mode 100644 src/app/components/identifier/identifier.component.css create mode 100644 src/app/components/identifier/identifier.component.html create mode 100644 src/app/components/identifier/identifier.component.spec.ts create mode 100644 src/app/components/identifier/identifier.component.ts create mode 100644 src/app/components/sidebar/sidebar.component.html create mode 100644 src/app/components/sidebar/sidebar.component.scss create mode 100644 src/app/components/sidebar/sidebar.component.spec.ts create mode 100644 src/app/components/sidebar/sidebar.component.ts diff --git a/angular.json b/angular.json index d05d884b..c9fe1fba 100644 --- a/angular.json +++ b/angular.json @@ -39,7 +39,9 @@ "node_modules/bootstrap-table/dist/bootstrap-table.min.css", "node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css", "node_modules/@ng-select/ng-select/themes/default.theme.css", - "node_modules/@uswds/uswds/dist/css/uswds.css" + "node_modules/@uswds/uswds/dist/css/uswds.css", + "node_modules/primeng/resources/themes/lara-light-blue/theme.css", + "node_modules/primeng/resources/primeng.min.css" ], "scripts": [ "node_modules/jquery/dist/jquery.min.js", diff --git a/package-lock.json b/package-lock.json index 5e88aaa9..c36a1b01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "passport-jwt": "^4.0.0", "path": "^0.12.7", "pdfjs-dist": "^2.16.105", + "primeng": "^17.18.10", "readline": "^1.3.0", "rxjs": "~6.5.4", "sqlstring": "^2.3.3", @@ -19456,6 +19457,21 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/primeng": { + "version": "17.18.10", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.10.tgz", + "integrity": "sha512-P3UskInOZ7qYICxSYvf0K8nUEb7DmndiXmyvLGU1wch+XcVWmVs4FZsWKNfdvK7TUdxxYj8WW44nodNV/epr3A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "rxjs": "^6.0.0 || ^7.8.1", + "zone.js": "~0.14.0" + } + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", diff --git a/package.json b/package.json index 1c8c043f..ee26c059 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "passport-jwt": "^4.0.0", "path": "^0.12.7", "pdfjs-dist": "^2.16.105", + "primeng": "^17.18.10", "readline": "^1.3.0", "rxjs": "~6.5.4", "sqlstring": "^2.3.3", diff --git a/src/app/app.component.css b/src/app/app.component.css deleted file mode 100644 index 32223bc5..00000000 --- a/src/app/app.component.css +++ /dev/null @@ -1,28 +0,0 @@ -.footer { - position: fixed; - right: 0; - left: 0; - bottom: 0; - color: #9d9d9d; - text-align: center; - background-color: #222; - border: 0; -} - -.vertical-center { - z-index: 100; - padding: 10px; - margin: 0; - position: absolute; - top: 50%; - right: -3%; - border: none; - transform: rotate(270deg) translateY(-50%); - cursor: pointer; - background: rgb(35, 120, 195); - font-size: 15px; - font-weight: bold; - color: rgb(255, 255, 255); - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 51ddc66a..0d347f78 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,24 +1,105 @@ - -
+ + + + + + +
+ + + + + +
- + - + +
+
+
+ + + + + + + + +
+
+
+
+ + + + + + +
GSA Enterprise Architecture 2024, v3.0
+
+ + diff --git a/src/app/app.component.scss b/src/app/app.component.scss new file mode 100644 index 00000000..58ccb865 --- /dev/null +++ b/src/app/app.component.scss @@ -0,0 +1,117 @@ +.content-body { + min-height: 100%; + display: flex; + justify-content: center; +} + +.footer { + color: #9d9d9d; + text-align: center; + background-color: #222; +} + + +/* .footer { + position: relative; + right: 0; + left: 0; + bottom: 0; + color: #9d9d9d; + text-align: center; + background-color: #222; + border: 0; +} */ + +.vertical-center { + z-index: 100; + padding: 10px; + margin: 0; + position: absolute; + top: 50%; + right: -3%; + border: none; + transform: rotate(270deg) translateY(-50%); + cursor: pointer; + background: rgb(35, 120, 195); + font-size: 15px; + font-weight: bold; + color: rgb(255, 255, 255); + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.identifier { + position: fixed; + right: 0; + left: 0; + bottom: 0; +} + +.sidebar-icon-container { + min-height: 100%; + padding-right: 4.5rem; +} + +.site-wrapper { + display: flex; +} + +.test-wrap { + position: fixed; + top: 65px; +} + +.sidebar-icon { + padding: 1rem; + color: white; + display: flex; + flex-direction: column; + + &.it-strategy { + background-color: #14883c; + &:hover { + background-color: #389f5c; + } + } + &.gsa-enterprise { + background-color: #2b60de; + &:hover { + background-color: #214599; + } + } + + &.business-systems { + background-color: #ce4844; + &:hover { + background-color: #c9302c; + } + } + + &.security { + background-color: #aa6708; + &:hover { + background-color: #ffbe2e; + } + } + + &.technologies { + background-color: #008080; + &:hover { + background-color: #076464; + } + } + + &.enterprise-architecture { + background-color: #800080; + &:hover { + background-color: #4d024d; + } + } + + &.additional-info { + background-color: #027eb2; + &:hover { + background-color: #00bde3; + } + } +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 146c7727..0410d0ab 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; +import { SharedService } from '@services/shared/shared.service'; import { distinctUntilChanged, filter } from 'rxjs/operators'; // Declare jQuery symbol @@ -9,10 +10,10 @@ declare var gtag: Function; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { - constructor(private router: Router) { + constructor(private router: Router, private sharedService: SharedService) { this.router.events.subscribe(event => { // Send page_view event to GA if (event instanceof NavigationEnd) { @@ -39,7 +40,7 @@ export class AppComponent implements OnInit { // SideNavbar Offset let footerElem: HTMLElement = document.getElementById('footer'); let sideNavElem: any = document.getElementsByTagName('ng-sidebar-container')[0]; - sideNavElem.style['height'] = `${window.innerHeight - topNavElem.offsetHeight - footerElem.offsetHeight}px`; + sideNavElem.style['height'] = `${window.innerHeight - topNavElem.offsetHeight - footerElem.offsetHeight + 10}px`; } showPopup(url, title, w, h) { @@ -67,4 +68,8 @@ export class AppComponent implements OnInit { } } + toggleSidebar() { + this.sharedService.toggleSidebar(); + } + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8f783ec9..8a389495 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,11 +7,13 @@ import { NgSelectModule } from '@ng-select/ng-select'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { SidebarModule } from 'ng-sidebar-v3'; // Sidebar Module +// import { SidebarModule } from 'ng-sidebar-v3'; // Sidebar Module import { PdfViewerModule } from 'ng2-pdf-viewer'; // PDF Viewer import { NgxChartsModule } from '@swimlane/ngx-charts'; // Visualizations import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { SidebarModule } from 'primeng/sidebar'; + // Components import { TopNavbarComponent } from './components/top-navbar/top-navbar.component'; import { SidenavComponent } from './components/sidenav/sidenav.component'; @@ -76,6 +78,9 @@ import { Globals } from './common/globals'; import { YesNoPipe } from "./pipes/yesno.pipe"; import { SkipFocusPiechartDirective } from '@common/skip-focus-piechart.directive'; import { BannerComponent } from './components/banner/banner.component'; +import { IdentifierComponent } from './components/identifier/identifier.component'; +import { SidebarComponent } from './components/sidebar/sidebar.component'; +import { AccordionModule } from 'primeng/accordion'; @NgModule({ declarations: [ AppComponent, @@ -122,6 +127,8 @@ import { BannerComponent } from './components/banner/banner.component'; YesNoPipe, SkipFocusPiechartDirective, BannerComponent, + IdentifierComponent, + SidebarComponent, ], bootstrap: [AppComponent], imports: [AppRoutingModule, BrowserAnimationsModule, @@ -131,7 +138,11 @@ import { BannerComponent } from './components/banner/banner.component'; NgxChartsModule, PdfViewerModule, ReactiveFormsModule, - SidebarModule.forRoot()], providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) + SidebarModule, + AccordionModule, + // SidebarModule.forRoot() + ], + providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) export class AppModule { constructor() {} } diff --git a/src/app/components/identifier/identifier.component.css b/src/app/components/identifier/identifier.component.css new file mode 100644 index 00000000..d2e3619d --- /dev/null +++ b/src/app/components/identifier/identifier.component.css @@ -0,0 +1,3 @@ +.usa-identifier { + text-align: left; +} \ No newline at end of file diff --git a/src/app/components/identifier/identifier.component.html b/src/app/components/identifier/identifier.component.html new file mode 100644 index 00000000..b86a7e4b --- /dev/null +++ b/src/app/components/identifier/identifier.component.html @@ -0,0 +1,103 @@ +
+
+
+
+ +
+
+

ea.gsa.gov

+

+ official website of the + U.S. General Services Administration +

+
+
+
+ +
+
+
+ Looking for U.S. government information and services?   +
+ Visit USA.gov +
+
+
\ No newline at end of file diff --git a/src/app/components/identifier/identifier.component.spec.ts b/src/app/components/identifier/identifier.component.spec.ts new file mode 100644 index 00000000..dc0e6445 --- /dev/null +++ b/src/app/components/identifier/identifier.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IdentifierComponent } from './identifier.component'; + +describe('IdentifierComponent', () => { + let component: IdentifierComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [IdentifierComponent] + }); + fixture = TestBed.createComponent(IdentifierComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/identifier/identifier.component.ts b/src/app/components/identifier/identifier.component.ts new file mode 100644 index 00000000..4ee9f7e5 --- /dev/null +++ b/src/app/components/identifier/identifier.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-identifier', + templateUrl: './identifier.component.html', + styleUrls: ['./identifier.component.css'] +}) +export class IdentifierComponent { + + constructor() { + } +} diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html new file mode 100644 index 00000000..0cb73f87 --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.html @@ -0,0 +1,124 @@ + + + + + + + + + + +
+ +
+
+
\ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.scss b/src/app/components/sidebar/sidebar.component.scss new file mode 100644 index 00000000..037b5b93 --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.scss @@ -0,0 +1,124 @@ +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + align-content: baseline; + padding: .5rem .75rem; +} + +.sidebar-link { + color: white; + font-family: Nunito Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol; + font-size: .875rem; + font-weight: 200; + letter-spacing: 1px; + line-height: 1.5; + margin: 0; + + &:hover { + cursor: pointer; + } + + &.it-strategy { + background-color: #14883c; + &:hover { + background-color: #389f5c; + } + } + + &.gsa-enterprise { + background-color: #2b60de; + &:hover { + background-color: #214599; + } + } + + &.business-systems { + background-color: #ce4844; + &:hover { + background-color: #c9302c; + } + } + + &.security { + background-color: #aa6708; + &:hover { + background-color: #ffbe2e; + } + } + + &.technologies { + background-color: #008080; + &:hover { + background-color: #076464; + } + } + + &.enterprise-architecture { + background-color: #800080; + &:hover { + background-color: #4d024d; + } + } + + &.additional-info { + background-color: #027eb2; + &:hover { + background-color: #00bde3; + } + } + + & .sidebar-sublink { + background-color: #343a40; + color: white; + margin: 0; + padding: 1.25rem 1rem; + display: block; + &:hover { + background-color: #1d2124; + } + } +} + +::ng-deep .p-sidebar { + // background: transparent; + border: none; + box-shadow: none; +} + +::ng-deep { + + & .p-accordion-header-text { + display: none; + } + + & .p-accordion { + + & p-accordiontab .p-accordion-tab { + margin: 0; + } + + & .p-accordion-header { + border: none; + border-radius: 0; + } + + & .p-accordion-header-link { + font-weight: 200; + border: 0; + background: none; + border-radius: 0; + padding: 1.25rem 1rem; + display: flex; + justify-content: space-between; + } + + & .p-accordion-content { + background-color: #343a40; + width: 100%; + border: none; + padding: 0; + margin: 0; + } + } +} \ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.spec.ts b/src/app/components/sidebar/sidebar.component.spec.ts new file mode 100644 index 00000000..9598903f --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SidebarComponent } from './sidebar.component'; + +describe('SidebarComponent', () => { + let component: SidebarComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SidebarComponent] + }); + fixture = TestBed.createComponent(SidebarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts new file mode 100644 index 00000000..e9a27a7b --- /dev/null +++ b/src/app/components/sidebar/sidebar.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { SharedService } from '@services/shared/shared.service'; + +@Component({ + selector: 'app-sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'] +}) +export class SidebarComponent { + + constructor(public sharedService: SharedService) { + } + + public close() { + this.sharedService.toggleSidebar(); + } + + public test() { + this.sharedService.toggleSidebar(); + } +} diff --git a/src/app/components/sidenav/sidenav.component.css b/src/app/components/sidenav/sidenav.component.css index ccd050c4..7c6f984b 100644 --- a/src/app/components/sidenav/sidenav.component.css +++ b/src/app/components/sidenav/sidenav.component.css @@ -2,11 +2,15 @@ ng-sidebar-container { /* Position needed to render sidebar properly */ - position: fixed; + /* position: fixed; */ /* Behind the top navbar */ z-index: 100; /* Scrollable contents if viewport is shorter than content. */ overflow-y: auto; + + position: relative; + width: 95vw; + height: 100vh; } /* Side Menu item*/ diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index a716c5c9..bf0bc4d9 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -1,5 +1,5 @@ - -
- + --> -

  • + -
  • + -
    + -
    - + --> - - + --> - + - + diff --git a/src/app/services/shared/shared.service.ts b/src/app/services/shared/shared.service.ts index 0b4ffe89..30ea1615 100644 --- a/src/app/services/shared/shared.service.ts +++ b/src/app/services/shared/shared.service.ts @@ -38,6 +38,8 @@ export class SharedService { websiteFormEmitter = new EventEmitter(); websiteFormSub: Subscription; + public sidebarVisible: boolean = false; + constructor( private globals: Globals, private location: Location, @@ -46,6 +48,11 @@ export class SharedService { ) { } + // new sidebar + public toggleSidebar() { + this.sidebarVisible = !this.sidebarVisible; + } + // Sidebar Toggle public toggleClick() { this.toggleEmitter.emit(); From 25af9fecc4ec3cb70ab63d0ab1e4cb3a75d7906e Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Thu, 26 Sep 2024 06:31:48 -0700 Subject: [PATCH 08/20] adding google api service and commented some of log stmts and removed google_auth go lang lib --- api/controllers/base.controller.js | 17 ++-- api/util/google-api-service.js | 62 ++++++++++++++ package.json | 1 + scripts/google_auth/GoogleAuth.go | 130 ----------------------------- scripts/google_auth/README.md | 67 --------------- scripts/google_auth/go.mod | 42 ---------- scripts/google_auth/go.sum | 115 ------------------------- scripts/google_auth/index.html | 59 ------------- server.js | 9 +- 9 files changed, 82 insertions(+), 420 deletions(-) create mode 100644 api/util/google-api-service.js delete mode 100644 scripts/google_auth/GoogleAuth.go delete mode 100644 scripts/google_auth/README.md delete mode 100644 scripts/google_auth/go.mod delete mode 100644 scripts/google_auth/go.sum delete mode 100644 scripts/google_auth/index.html diff --git a/api/controllers/base.controller.js b/api/controllers/base.controller.js index b55e8961..9cababa5 100644 --- a/api/controllers/base.controller.js +++ b/api/controllers/base.controller.js @@ -119,9 +119,9 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul formattedDate = `${date.getFullYear()}${String((date.getMonth()+1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}${String(date.getMinutes()).padStart(2, "0")}`; } - sql.query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`, (error, data) => { + //sql.query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`, (error, data) => { - if (error) { + /*if (error) { console.log(`Duplicate Google Sheets API Request: `, error); if (requester === "GearCronJ") { @@ -134,7 +134,7 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul } else { // log the start of the refresh to the database buildLogQuery(sql, `Update All Related Records - Starting`, requester, "log_update_zk_systems_subsystems_records", response); - +*/ // Load client secrets from a local file. fs.readFile("certs/gear_google_credentials.json", (err, content) => { if (err) { @@ -169,8 +169,9 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul key ); }); - } - }); + + //} + //}); }; /** @@ -200,6 +201,7 @@ function authorize( try { // Check if we have previously stored a token. fs.readFile(TOKEN_PATH, (err, token) => { + console.log(err); if (err) { let errMessage = "Reading the Token returned an error: " + err; errMessage = errMessage.replace(/'/g, ""); @@ -282,6 +284,11 @@ function refresh(auth, response, sheetID, dataRange, requester) { // If there is an error with the API call to the spreadsheet return the error if (err) { + console.log("google api error .....") + console.log(sheetID) + console.log(dataRange) + console.log(err) + console.log("google api error end...") buildLogQuery(sql, `Update All Related Records - ERROR: Google Sheets API returned...\n${err.message}`, requester, "log_update_zk_systems_subsystems_records", response); if (requester === "GearCronJ") { diff --git a/api/util/google-api-service.js b/api/util/google-api-service.js new file mode 100644 index 00000000..555bc211 --- /dev/null +++ b/api/util/google-api-service.js @@ -0,0 +1,62 @@ +const { GoogleAuth } = require('google-auth-library'); +const path = require('path'); +const fs = require('fs'); +const {google} = require('googleapis'); + +const sheets = google.sheets('v4'); + +const TOKEN_PATH = "./token.json" + + +const getClient = async () => { + const auth = new GoogleAuth({ + keyFile: path.join(__dirname, '../../certs/gear-google-auth-client-credentials.json'), + scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'], + }); + + const projectId = await auth.getProjectId(); + console.log('Project ID:', projectId); + + const client = await auth.getClient(); + console.log('Authenticated client:', client); + return client; +} + +const authenticate = async () => { + const client = await getClient() + await client.authorize(); + return client.credentials; +} + +exports.getToken = async () => { + const credentials = await authenticate(); + return client.credentials.access_token; +} + +exports.saveToken = async () => { + const credentials = await authenticate(); + fs.writeFile(TOKEN_PATH, JSON.stringify(credentials), (err) => { + if (err) { + console.log(err); + throw err; + } + console.log('Token stored to', TOKEN_PATH); + }); +} + +exports.getSheetInfo = async (spreadsheetId) => { + const authClient = await getClient(); + const request = { + spreadsheetId: spreadsheetId, + ranges: ["Master Junction with Business Systems!A2:B"], + auth: authClient, + }; + + try { + const response = await sheets.spreadsheets.get(request); + console.log('Spreadsheet Info:', response.data); + return data; + } catch (err) { + console.error('Error retrieving spreadsheet info:', err); + } +} diff --git a/package.json b/package.json index 1c8c043f..c33d3222 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "express-rate-limit": "^6.7.0", "fast-csv": "^4.3.6", "follow-redirects": "^1.14.7", + "google-auth-library": "^9.14.1", "googleapis": "^114.0.0", "jexl": "^2.3.0", "jquery": "^3.6.0", diff --git a/scripts/google_auth/GoogleAuth.go b/scripts/google_auth/GoogleAuth.go deleted file mode 100644 index 47158043..00000000 --- a/scripts/google_auth/GoogleAuth.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -import ( - "context" - "fmt" - "html/template" - "log" - "net/http" - "net/url" - "os" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" -) - -var scopes = []string{"https://www.googleapis.com/auth/spreadsheets.readonly"} -var err error -var clientSecretData []byte -var CONFIG *oauth2.Config - -// init is called before main -func init() { - // Read the client secret file - clientSecretData, err = os.ReadFile("gear_google_credentials.json") - if err != nil { - log.Printf("Unable to read client secret file: %v", err) - panic(err) - } - - // Get the config from the client secret data - log.Println("Obtaining config from client secret data...") - CONFIG, err = google.ConfigFromJSON(clientSecretData, scopes...) - if err != nil { - log.Fatalf("Unable to parse client secret file to config: %v", err) - } -} - -func main() { - //Start the server - router := gin.Default() - CORS := cors.DefaultConfig() - CORS.AllowHeaders = []string{"*"} //Allows all headers for things like htmx's hx-get, hx-target, etc - CORS.AllowAllOrigins = true - router.Use(cors.New(CORS)) - - router.LoadHTMLGlob("*.html") - router.GET("/", indexPage) - router.GET("/beginAuth", BeginAuth) - router.POST("/getToken", GetToken) - - router.Run(":4201") - log.Println("Listening on port 4201...") -} - -func indexPage(c *gin.Context) { - // Define the data to be injected into the template - - // Parse the HTML template file - tmpl, err := template.ParseFiles("index.html") // Use "template.html" here - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - // Execute the template with the data - c.HTML(http.StatusOK, tmpl.Name(), struct { - ClientID string - }{ - ClientID: CONFIG.ClientID, - }) // Use "template.html" here as well -} - -// BeginAuth begins the authorization process -func BeginAuth(c *gin.Context) { - // Get the config from the client secret data - log.Println("Obtaining config from client secret data...") - config, err := google.ConfigFromJSON(clientSecretData, scopes...) - if err != nil { - log.Fatalf("Unable to parse client secret file to config: %v", err) - } - - // Get the token from the user - authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) - log.Println("Opening browser to obtain token...") - html := ` - - - - Open URL - - - - ` - - c.Writer.WriteHeader(http.StatusOK) - c.Writer.Header().Set("Content-Type", "text/html") - fmt.Fprint(c.Writer, html) -} - -// GetToken saves the token to a file -func GetToken(c *gin.Context) { - - // Parse the URL - parsedURL, err := url.Parse(c.PostForm("token_string")) - if err != nil { - fmt.Println("Error parsing URL:", err) - return - } - - // Get the "code" query parameter - code := parsedURL.Query().Get("code") - - // Get the token from the user - log.Println("Getting token from user...") - token, err := CONFIG.Exchange(context.Background(), code) - if err != nil { - log.Println(err.Error()) - c.JSON(http.StatusBadRequest, gin.H{ - "error": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, token) - -} diff --git a/scripts/google_auth/README.md b/scripts/google_auth/README.md deleted file mode 100644 index d975cce7..00000000 --- a/scripts/google_auth/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Go OAuth2 for Google Spreadsheets - -This application demonstrates how to integrate OAuth2 for Google services, specifically for Google Spreadsheets, using the Gin web framework. - -## Dependencies: - -- `gin-gonic/gin`: The Gin web framework for building web applications. -- `gin-contrib/cors`: Middleware to handle Cross-Origin Resource Sharing in Gin. -- `golang.org/x/oauth2` and `golang.org/x/oauth2/google`: Libraries for integrating OAuth2 and specifically the Google authentication flow. - -## Structure: - -- **Initialization (`init` function)**: - - Reads the `gear_google_credentials.json` containing client secret data for OAuth2. - - Sets up the OAuth2 configuration using the client secret. - -- **Main (`main` function)**: - - Sets up routes and middleware. - - Starts the server listening on port `4201`. - -## Endpoints: - -1. **`GET /`**: - - Loads the `index.html` template and injects the OAuth2 Client ID. - -2. **`GET /beginAuth`**: - - Begins the OAuth2 authentication process by redirecting the user to Google's authentication page. - -3. **`POST /getToken`**: - - Accepts the token string from the client. - - Exchanges the received code for an authentication token. - - Sends back the token as a JSON response. - -## Configuration: - -Ensure you have a `gear_google_credentials.json` file in your root directory which contains your Google client secret data. - -## Run: - -Follow the steps below to run the application. - -1. Navigate to the `./scripts/google_auth` folder in the file explorer. - -2. Make sure to have a copy of the `gear_google_credentials.json` file in this folder before running. - -3. Open a command line prompt from this folder. - -4. Run the command `go run GoogleAuth.go` to start the application. - -## Usage: - -Run the application and navigate to `http://localhost:4201/` to begin the OAuth2 authentication process. - -### Getting Started: - - -1. **Start Authentication**: - - On the main page, click on the "Begin OAuth Flow" link. - -3. **Google Authentication**: - - You'll be redirected to Google's authentication page. If you're not already logged in to a Google account, you'll be prompted to do so. - - After logging in, Google will ask for permissions to access your Google Sheets data. Review the permissions and click "Allow" if you're okay with the requested access. - -4. **Receive Token**: - - After granting permissions, you'll be redirected to a empty page. Copy the URL on this page and return back to the Google Authentication Tool page. - - In the "Generate Token" section, paste the url then click "GenerateToken" - - The Token will appear at the bottom of the page. Copy the token and save it in a new .json file for use with Google APIs. diff --git a/scripts/google_auth/go.mod b/scripts/google_auth/go.mod deleted file mode 100644 index 05f2224d..00000000 --- a/scripts/google_auth/go.mod +++ /dev/null @@ -1,42 +0,0 @@ -module googleauthentication - -go 1.20 - -require ( - github.com/gin-contrib/cors v1.6.0 - github.com/gin-gonic/gin v1.9.1 - golang.org/x/oauth2 v0.11.0 -) - -require ( - cloud.google.com/go/compute v1.23.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/bytedance/sonic v1.11.2 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.19.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/scripts/google_auth/go.sum b/scripts/google_auth/go.sum deleted file mode 100644 index ae463380..00000000 --- a/scripts/google_auth/go.sum +++ /dev/null @@ -1,115 +0,0 @@ -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= -github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg= -github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/scripts/google_auth/index.html b/scripts/google_auth/index.html deleted file mode 100644 index 51a1df58..00000000 --- a/scripts/google_auth/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - Google Auth - - - -
    -

    Client ID: {{.ClientID}}

    -
    - -

    Google Authentication Tool

    -
    -
    -
    -

    OAuth2

    -
    -

    - This tool will generate a URL that you can use to authenticate with Google. You will need to provide a client ID - and client secret. -

    -
    - -
    -
    -
    -
    -

    Generate Token

    -
    -

    - Provide the url from the redirect after authenticating with Google. -

    -
    -
    - -
    -
    - -
    -

    - The token will be displayed below. -

    - -
    -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/server.js b/server.js index da31a3b4..f12a6bc9 100644 --- a/server.js +++ b/server.js @@ -19,6 +19,7 @@ const bodyParser = require('body-parser'), api = require('./api/index'), + googleApiService = require('./api/util/google-api-service.js') port = process.env.PORT || 3000, ExtractJWT = passportJWT.ExtractJwt, @@ -507,9 +508,13 @@ cron.schedule(process.env.POC_CRON, () => { //PRODUCTION // ------------------------------------------------------------------------------------------------- // CRON JOB: Google Sheets API - Update All Related Records (runs every weekday at 11:00 PM) -cron.schedule(process.env.RECORDS_CRON, () => { +cron.schedule("* * * * *", async () => { + const sheetId = '1eSoyn7-2EZMqbohzTMNDcFmNBbkl-CzXtpwNXHNHD1A'; + console.log(googleServiceClient.getSheetInfo(sheetId)); + + //await googleAuth.saveToken().catch(console.error); //cron.schedule('50 14 * * 1-5', () => { //DEBUGGING - cronCtrl.runUpdateAllRelatedRecordsJob(); + //cronCtrl.runUpdateAllRelatedRecordsJob(); }); // ------------------------------------------------------------------------------------------------- From db8952b11822509a90c5c94514c52bea5537fb06 Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:56:44 -0700 Subject: [PATCH 09/20] update google api call udpates. --- api/controllers/base.controller.js | 5 ++--- api/util/google-api-service.js | 6 +++--- server.js | 10 +++------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/api/controllers/base.controller.js b/api/controllers/base.controller.js index 9cababa5..ed28a764 100644 --- a/api/controllers/base.controller.js +++ b/api/controllers/base.controller.js @@ -201,7 +201,6 @@ function authorize( try { // Check if we have previously stored a token. fs.readFile(TOKEN_PATH, (err, token) => { - console.log(err); if (err) { let errMessage = "Reading the Token returned an error: " + err; errMessage = errMessage.replace(/'/g, ""); @@ -284,11 +283,11 @@ function refresh(auth, response, sheetID, dataRange, requester) { // If there is an error with the API call to the spreadsheet return the error if (err) { - console.log("google api error .....") + console.log("Google api error::: Start") console.log(sheetID) console.log(dataRange) console.log(err) - console.log("google api error end...") + console.log("Google api error::: End") buildLogQuery(sql, `Update All Related Records - ERROR: Google Sheets API returned...\n${err.message}`, requester, "log_update_zk_systems_subsystems_records", response); if (requester === "GearCronJ") { diff --git a/api/util/google-api-service.js b/api/util/google-api-service.js index 555bc211..c209e6e7 100644 --- a/api/util/google-api-service.js +++ b/api/util/google-api-service.js @@ -44,18 +44,18 @@ exports.saveToken = async () => { }); } -exports.getSheetInfo = async (spreadsheetId) => { +exports.getSheetInfo = async (spreadsheetId, dataRange) => { const authClient = await getClient(); const request = { spreadsheetId: spreadsheetId, - ranges: ["Master Junction with Business Systems!A2:B"], + ranges: [dataRange], auth: authClient, }; try { const response = await sheets.spreadsheets.get(request); console.log('Spreadsheet Info:', response.data); - return data; + return response.data; } catch (err) { console.error('Error retrieving spreadsheet info:', err); } diff --git a/server.js b/server.js index f12a6bc9..1f4d19b3 100644 --- a/server.js +++ b/server.js @@ -508,13 +508,9 @@ cron.schedule(process.env.POC_CRON, () => { //PRODUCTION // ------------------------------------------------------------------------------------------------- // CRON JOB: Google Sheets API - Update All Related Records (runs every weekday at 11:00 PM) -cron.schedule("* * * * *", async () => { - const sheetId = '1eSoyn7-2EZMqbohzTMNDcFmNBbkl-CzXtpwNXHNHD1A'; - console.log(googleServiceClient.getSheetInfo(sheetId)); - - //await googleAuth.saveToken().catch(console.error); -//cron.schedule('50 14 * * 1-5', () => { //DEBUGGING - //cronCtrl.runUpdateAllRelatedRecordsJob(); +cron.schedule(process.env.RECORDS_CRON, async () => { + await googleApiService.saveToken().catch(console.error); + cronCtrl.runUpdateAllRelatedRecordsJob(); }); // ------------------------------------------------------------------------------------------------- From fd061fe09a26835984ce6df880ef7c4d3a421756 Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:00:50 -0700 Subject: [PATCH 10/20] commenting code about duplicate row check using formatted date value --- api/controllers/base.controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/controllers/base.controller.js b/api/controllers/base.controller.js index ed28a764..0b1e2de3 100644 --- a/api/controllers/base.controller.js +++ b/api/controllers/base.controller.js @@ -119,9 +119,9 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul formattedDate = `${date.getFullYear()}${String((date.getMonth()+1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}${String(date.getMinutes()).padStart(2, "0")}`; } - //sql.query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`, (error, data) => { + /*sql.query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`, (error, data) => { - /*if (error) { + if (error) { console.log(`Duplicate Google Sheets API Request: `, error); if (requester === "GearCronJ") { @@ -170,8 +170,8 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul ); }); - //} - //}); + /*} + });*/ }; /** From 452aee9cf69932c39ac22f3c2d55ec281b088807 Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:03:19 -0700 Subject: [PATCH 11/20] commenting debug stmts --- api/util/google-api-service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/util/google-api-service.js b/api/util/google-api-service.js index c209e6e7..db305a9a 100644 --- a/api/util/google-api-service.js +++ b/api/util/google-api-service.js @@ -15,10 +15,10 @@ const getClient = async () => { }); const projectId = await auth.getProjectId(); - console.log('Project ID:', projectId); + //console.log('Project ID:', projectId); const client = await auth.getClient(); - console.log('Authenticated client:', client); + //console.log('Authenticated client:', client); return client; } @@ -54,7 +54,7 @@ exports.getSheetInfo = async (spreadsheetId, dataRange) => { try { const response = await sheets.spreadsheets.get(request); - console.log('Spreadsheet Info:', response.data); + //console.log('Spreadsheet Info:', response.data); return response.data; } catch (err) { console.error('Error retrieving spreadsheet info:', err); From c93b4988679a855de463a58c581ad3f560e25c3f Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Mon, 30 Sep 2024 10:37:00 -0400 Subject: [PATCH 12/20] added keyboard functionality to sidebar. removed old sidenav and updated some clas and fn names --- package-lock.json | 13 - package.json | 1 - src/app/app.component.html | 161 +++---- src/app/app.component.scss | 45 +- src/app/app.component.ts | 8 +- src/app/app.module.ts | 4 - .../components/sidebar/sidebar.component.html | 13 +- .../components/sidebar/sidebar.component.scss | 22 +- .../components/sidebar/sidebar.component.ts | 6 +- .../components/sidenav/sidenav.component.css | 40 -- .../components/sidenav/sidenav.component.html | 416 ------------------ .../sidenav/sidenav.component.spec.ts | 25 -- .../components/sidenav/sidenav.component.ts | 95 ---- src/app/services/shared/shared.service.ts | 9 +- 14 files changed, 131 insertions(+), 727 deletions(-) delete mode 100644 src/app/components/sidenav/sidenav.component.css delete mode 100644 src/app/components/sidenav/sidenav.component.html delete mode 100644 src/app/components/sidenav/sidenav.component.spec.ts delete mode 100644 src/app/components/sidenav/sidenav.component.ts diff --git a/package-lock.json b/package-lock.json index c36a1b01..a9e1d274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,6 @@ "ldapjs": "^2.3.1", "make-fetch-happen": "^10.1.2", "mysql2": "^3.9.7", - "ng-sidebar-v3": "^18.0.0", "ng2-pdf-viewer": "^10.2.2", "ngx-cookie-service": "^18.0.0", "node-cron": "^2.0.3", @@ -14888,18 +14887,6 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, - "node_modules/ng-sidebar-v3": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/ng-sidebar-v3/-/ng-sidebar-v3-18.0.0.tgz", - "integrity": "sha512-LrKs7OgTTRpxUdvjekHgybXzSKS0cwKXo+J8sH3BCLUob0OhudljB3n8Hf6xUDF7NHM204PKdRT7l8sl5tXElA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^18.0.6", - "@angular/core": "^18.0.6" - } - }, "node_modules/ng2-pdf-viewer": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.2.2.tgz", diff --git a/package.json b/package.json index ee26c059..f04b0657 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "ldapjs": "^2.3.1", "make-fetch-happen": "^10.1.2", "mysql2": "^3.9.7", - "ng-sidebar-v3": "^18.0.0", "ng2-pdf-viewer": "^10.2.2", "ngx-cookie-service": "^18.0.0", "node-cron": "^2.0.3", diff --git a/src/app/app.component.html b/src/app/app.component.html index 0d347f78..fbe2661e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,105 +1,88 @@ - - - - + -
    - - - - - -
    -
    - - - - -
    -
    -
    - -
    -
    -
    - -
    - - - - - - - - - -
    -
    - GSA Enterprise Architecture 2024, v3.0 +
    + - -
    - - + +
    +
    + GSA Enterprise Architecture 2024, v3.0 +
    + + +
    + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 58ccb865..d53f30ce 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -8,25 +8,15 @@ color: #9d9d9d; text-align: center; background-color: #222; + display: flex; + flex-direction: column; } - -/* .footer { - position: relative; - right: 0; - left: 0; - bottom: 0; - color: #9d9d9d; - text-align: center; - background-color: #222; - border: 0; -} */ - .vertical-center { z-index: 100; padding: 10px; margin: 0; - position: absolute; + position: fixed; top: 50%; right: -3%; border: none; @@ -49,20 +39,32 @@ .sidebar-icon-container { min-height: 100%; - padding-right: 4.5rem; + padding-right: 4.1rem; + + & .icon-break { + padding: .78rem; + } } -.site-wrapper { +.sidebar-content-wrapper { display: flex; } -.test-wrap { +.sidebar-icon-wrap { position: fixed; - top: 65px; + top: 67px; + + @media (max-width: 1121px) { + top: 95px; + } } .sidebar-icon { - padding: 1rem; + padding: 1.32rem; + font-size: .875rem; + font-weight: 200; + letter-spacing: 1px; + line-height: 1.5; color: white; display: flex; flex-direction: column; @@ -114,4 +116,11 @@ background-color: #00bde3; } } +} + +.page-wrap { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; } \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0410d0ab..7d7608ac 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -39,7 +39,7 @@ export class AppComponent implements OnInit { // SideNavbar Offset let footerElem: HTMLElement = document.getElementById('footer'); - let sideNavElem: any = document.getElementsByTagName('ng-sidebar-container')[0]; + let sideNavElem: any = document.getElementsByTagName('p-sidebar')[0]; sideNavElem.style['height'] = `${window.innerHeight - topNavElem.offsetHeight - footerElem.offsetHeight + 10}px`; } @@ -72,4 +72,10 @@ export class AppComponent implements OnInit { this.sharedService.toggleSidebar(); } + onSidebarIconsKeyDown(e: KeyboardEvent) { + if(e.code === 'Space' || e.code === 'Enter') { + this.toggleSidebar(); + } + } + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8a389495..e5f595da 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,7 +7,6 @@ import { NgSelectModule } from '@ng-select/ng-select'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -// import { SidebarModule } from 'ng-sidebar-v3'; // Sidebar Module import { PdfViewerModule } from 'ng2-pdf-viewer'; // PDF Viewer import { NgxChartsModule } from '@swimlane/ngx-charts'; // Visualizations import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -16,7 +15,6 @@ import { SidebarModule } from 'primeng/sidebar'; // Components import { TopNavbarComponent } from './components/top-navbar/top-navbar.component'; -import { SidenavComponent } from './components/sidenav/sidenav.component'; //// Main import { HomeComponent } from './views/main/home/home.component'; @@ -85,7 +83,6 @@ import { AccordionModule } from 'primeng/accordion'; @NgModule({ declarations: [ AppComponent, TopNavbarComponent, - SidenavComponent, HomeComponent, GlobalSearchComponent, AboutComponent, @@ -140,7 +137,6 @@ import { AccordionModule } from 'primeng/accordion'; ReactiveFormsModule, SidebarModule, AccordionModule, - // SidebarModule.forRoot() ], providers: [Globals, provideHttpClient(withInterceptorsFromDi())] }) export class AppModule { diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html index 0cb73f87..aedfa0ce 100644 --- a/src/app/components/sidebar/sidebar.component.html +++ b/src/app/components/sidebar/sidebar.component.html @@ -1,17 +1,11 @@ - + - + \ No newline at end of file diff --git a/src/app/components/sidebar/sidebar.component.scss b/src/app/components/sidebar/sidebar.component.scss index 037b5b93..c88c3194 100644 --- a/src/app/components/sidebar/sidebar.component.scss +++ b/src/app/components/sidebar/sidebar.component.scss @@ -1,9 +1,13 @@ .sidebar-header { display: flex; - justify-content: space-between; + justify-content: end; align-items: center; align-content: baseline; - padding: .5rem .75rem; + padding: .25rem .5rem; +} + +.sidebar-content-wrapper { + margin-bottom: 200px; } .sidebar-link { @@ -32,7 +36,7 @@ background-color: #214599; } } - + &.business-systems { background-color: #ce4844; &:hover { @@ -81,9 +85,19 @@ } ::ng-deep .p-sidebar { - // background: transparent; border: none; box-shadow: none; + overflow-y: auto; + top: 67px; + // background: transparent; + + @media (max-width: 1121px) { + top: 95px; + } + + @media (max-width: 1004px) { + top: 98px; + } } ::ng-deep { diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts index e9a27a7b..784862dd 100644 --- a/src/app/components/sidebar/sidebar.component.ts +++ b/src/app/components/sidebar/sidebar.component.ts @@ -11,11 +11,7 @@ export class SidebarComponent { constructor(public sharedService: SharedService) { } - public close() { - this.sharedService.toggleSidebar(); - } - - public test() { + public closeSidebar() { this.sharedService.toggleSidebar(); } } diff --git a/src/app/components/sidenav/sidenav.component.css b/src/app/components/sidenav/sidenav.component.css deleted file mode 100644 index 7c6f984b..00000000 --- a/src/app/components/sidenav/sidenav.component.css +++ /dev/null @@ -1,40 +0,0 @@ -/* Sidebar */ - -ng-sidebar-container { - /* Position needed to render sidebar properly */ - /* position: fixed; */ - /* Behind the top navbar */ - z-index: 100; - /* Scrollable contents if viewport is shorter than content. */ - overflow-y: auto; - - position: relative; - width: 95vw; - height: 100vh; -} - -/* Side Menu item*/ - -ng-sidebar .list-group a { - color: white; -} - -/* Give some more space for the text and icons */ - -.list-group-item { - padding-right: 15px; -} - -/* Fix GEAR Manager Login footer to bottom */ - -.sidebar-footer { - position: absolute; - width: 100%; - bottom: 0; -} - -/* Expand Width of Toast Notifications */ - -.toast { - max-width: 50% !important; -} \ No newline at end of file diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html deleted file mode 100644 index bf0bc4d9..00000000 --- a/src/app/components/sidenav/sidenav.component.html +++ /dev/null @@ -1,416 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/components/sidenav/sidenav.component.spec.ts b/src/app/components/sidenav/sidenav.component.spec.ts deleted file mode 100644 index f6bddf62..00000000 --- a/src/app/components/sidenav/sidenav.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { SidenavComponent } from './sidenav.component'; - -describe('SidenavComponent', () => { - let component: SidenavComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ SidenavComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SidenavComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/sidenav/sidenav.component.ts b/src/app/components/sidenav/sidenav.component.ts deleted file mode 100644 index 59c52fd2..00000000 --- a/src/app/components/sidenav/sidenav.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { SharedService } from '@services/shared/shared.service'; - -// Declare jQuery symbol -declare var $: any; - -@Component({ - selector: 'sidenav', - templateUrl: './sidenav.component.html', - styleUrls: ['./sidenav.component.css'] -}) -export class SidenavComponent implements OnInit { - - constructor(public sharedService: SharedService) { } - - ngOnInit(): void { - if (this.sharedService.toggleSub == undefined) { - this.sharedService.toggleSub = this.sharedService.toggleEmitter.subscribe(() => { this._toggleOpened(); }); - } - - $(document).ready(() => { - // Logged in collapse notification - $('#loggedIn').on('show.bs.toast', function () { - $('#loggedInCollapse').collapse('show') - }); - $('#loggedIn').on('hide.bs.toast', function () { - $('#loggedInCollapse').collapse('hide') - }); - - // Logged out collapse notification - $('#loggedOut').on('show.bs.toast', function () { - $('#loggedOutCollapse').collapse('show') - }); - $('#loggedOut').on('hide.bs.toast', function () { - $('#loggedOutCollapse').collapse('hide') - }); - }); - } - - _opened: boolean = false; - _mode: string = 'push'; - _position: string = 'left'; - _dock: boolean = true; - _dockedSize: string = '55px'; - _closeOnClickOutside: boolean = true; - _closeOnClickBackdrop: boolean = false; - _showBackdrop: boolean = false; - _animate: boolean = true; - _trapFocus: boolean = true; - _autoFocus: boolean = true; - _keyClose: boolean = false; - _autoCollapseHeight: number = 500; - _autoCollapseWidth: number = 500; - - _toggleOpened(): void { - this._opened = !this._opened; - } - - _onOpenStart(): void { - console.info('Sidebar opening'); - $("#sidebarToggle").addClass("opposite"); // Rotate arrow inward - } - - _onOpened(): void { - console.info('Sidebar opened'); - } - - _onCloseStart(): void { - console.info('Sidebar closing'); - $('#strategyDropdown').collapse('hide'); - $('#enterpriseDropdown').collapse('hide'); - $('#systemsDropdown').collapse('hide'); - $('#securityDropdown').collapse('hide'); - $('#technologiesDropdown').collapse('hide'); - $('#eaDropdown').collapse('hide'); - $('#addinfoDropdown').collapse('hide'); - - $("#sidebarToggle").removeClass("opposite"); // Rotate arrow outward - } - - _onClosed(): void { - console.info('Sidebar closed'); - $(window).trigger("resize"); - } - - _onTransitionEnd(): void { - console.info('Transition ended'); - $(window).trigger("resize"); - } - - _onBackdropClicked(): void { - console.info('Backdrop clicked'); - } - -} diff --git a/src/app/services/shared/shared.service.ts b/src/app/services/shared/shared.service.ts index 30ea1615..88bfe2e7 100644 --- a/src/app/services/shared/shared.service.ts +++ b/src/app/services/shared/shared.service.ts @@ -48,16 +48,11 @@ export class SharedService { ) { } - // new sidebar + // Toggle sidebar open/closed public toggleSidebar() { - this.sidebarVisible = !this.sidebarVisible; + this.sidebarVisible = !this.sidebarVisible; } - // Sidebar Toggle - public toggleClick() { - this.toggleEmitter.emit(); - }; - // File Name Formatting public fileNameFmt(name: string): string { // Append current date time to filename From 2572bb88b3cc972d0688dad8b6552d7c73f1b170 Mon Sep 17 00:00:00 2001 From: Jonah Hatfield Date: Tue, 1 Oct 2024 10:59:40 -0400 Subject: [PATCH 13/20] Patching body-parser and path-to-regexp packages due to GitHub Dependabot high severity vulnerabilities, satisfying issue #498 --- package-lock.json | 470 ++++++++++++++++++++++++++++++++++++++++------ package.json | 3 +- 2 files changed, 411 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9e1d274..494b68f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "adjust-sourcemap-loader": "^4.0.0", "async": "^2.6.4", "base-64": "^0.1.0", - "body-parser": "^1.19.0", + "body-parser": "^1.20.3", "bootstrap": "^4.6.0", "bootstrap-datepicker": "^1.9.0", "bootstrap-table": "^1.18.3", @@ -48,6 +48,7 @@ "express-rate-limit": "^6.7.0", "fast-csv": "^4.3.6", "follow-redirects": "^1.14.7", + "google-auth-library": "^9.14.1", "googleapis": "^114.0.0", "jexl": "^2.3.0", "jquery": "^3.6.0", @@ -68,6 +69,7 @@ "passport": "^0.4.1", "passport-jwt": "^4.0.0", "path": "^0.12.7", + "path-to-regexp": "^0.1.10", "pdfjs-dist": "^2.16.105", "primeng": "^17.18.10", "readline": "^1.3.0", @@ -8295,9 +8297,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "engines": { "node": "*" } @@ -8337,9 +8339,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8349,7 +8351,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -8372,6 +8374,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -11115,6 +11131,29 @@ "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-1.0.0.tgz", "integrity": "sha512-zXSSClWBPfcSYjg0hcQNompkFN/MxQQ53eyrzm9BYgik2ut2I7PxAf2foVqBRMYCwWaZx/aWodi+uk76npdSAw==" }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -11128,6 +11167,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11546,15 +11590,65 @@ } }, "node_modules/gcp-metadata": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", - "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", "dependencies": { - "gaxios": "^5.0.0", + "gaxios": "^6.0.0", "json-bigint": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" + } + }, + "node_modules/gcp-metadata/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/generate-function": { @@ -11681,30 +11775,57 @@ } }, "node_modules/google-auth-library": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", - "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", "dependencies": { - "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/google-auth-library/node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "node_modules/google-auth-library/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, "engines": { - "node": ">=8" + "node": ">= 14" + } + }, + "node_modules/google-auth-library/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/google-auth-library/node_modules/jwa": { @@ -11726,26 +11847,23 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/google-auth-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "node_modules/google-auth-library/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/google-auth-library/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/google-p12-pem": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "deprecated": "Package is no longer maintained", "dependencies": { "node-forge": "^1.3.1" }, @@ -11784,6 +11902,88 @@ "node": ">=12.0.0" } }, + "node_modules/googleapis-common/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/googleapis-common/node_modules/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -11792,6 +11992,98 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/googleapis-common/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/googleapis/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -11809,16 +12101,53 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", + "gaxios": "^6.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gtoken/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/gtoken/node_modules/jwa": { @@ -11840,6 +12169,18 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/gtoken/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -18369,9 +18710,12 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -19127,9 +19471,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -20829,13 +21173,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/package.json b/package.json index 9f100091..a15d18e2 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "adjust-sourcemap-loader": "^4.0.0", "async": "^2.6.4", "base-64": "^0.1.0", - "body-parser": "^1.19.0", + "body-parser": "^1.20.3", "bootstrap": "^4.6.0", "bootstrap-datepicker": "^1.9.0", "bootstrap-table": "^1.18.3", @@ -82,6 +82,7 @@ "passport": "^0.4.1", "passport-jwt": "^4.0.0", "path": "^0.12.7", + "path-to-regexp": "^0.1.10", "pdfjs-dist": "^2.16.105", "primeng": "^17.18.10", "readline": "^1.3.0", From dcaa38c75950f0e9e647044e8cffbe88d3f2a2d7 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Wed, 2 Oct 2024 14:57:21 -0400 Subject: [PATCH 14/20] added ability to turn filters on/off --- src/app/components/table/table.component.html | 3 ++- src/app/components/table/table.component.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 0d50069d..ed05435b 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -42,6 +42,7 @@ + @@ -56,7 +57,7 @@ - + diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 1c1690bf..ff66253c 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -32,6 +32,7 @@ export class TableComponent implements OnInit { exportColumns!: ExportColumn[]; currentButtonFilter: string = ''; screenHeight: string = ''; + showFilters: boolean = false; constructor() { this.screenHeight = `${(window.innerHeight - 500)}px`; @@ -59,6 +60,10 @@ export class TableComponent implements OnInit { this.isPaginated = !this.isPaginated; } + toggleFilter() { + this.showFilters = !this.showFilters; + } + getExportFilename(reportName: string) { let today = new Date(); let year = today.getFullYear(); From 07706707c8dda41aeed812df7cc450f2f5457333 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Wed, 2 Oct 2024 15:56:58 -0400 Subject: [PATCH 15/20] fixed stickyheader offset --- src/app/services/shared/shared.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/services/shared/shared.service.ts b/src/app/services/shared/shared.service.ts index 88bfe2e7..49ef5b47 100644 --- a/src/app/services/shared/shared.service.ts +++ b/src/app/services/shared/shared.service.ts @@ -400,13 +400,12 @@ export class SharedService { return cookies; } - public enableStickyHeader(tableComponentId: string, closestScrollableClass: string = '.ng-sidebar__content') { + public enableStickyHeader(tableComponentId: string, closestScrollableClass: string = '.content-body') { $('#'+tableComponentId).floatThead({ - scrollContainer: function($table) { + responsiveContainer: function($table) { return $table.closest(closestScrollableClass); }, - position: "fixed", - autoReflow: true + top: 67 // top navbar offset }); } From bcc69a63748c8e806ab51158dd1c0fb5b7774454 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Mon, 14 Oct 2024 12:44:32 -0400 Subject: [PATCH 16/20] added report style --- src/app/components/table/table.component.html | 3 ++- src/app/components/table/table.component.scss | 9 +++++++++ src/app/components/table/table.component.ts | 1 + .../data-dictionary-primeng.component.html | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index ed05435b..52775837 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -15,7 +15,8 @@ [scrollable]="true" [scrollHeight]="screenHeight" [exportHeader]="'customExportHeader'" -[exportFilename]="getExportFilename('Data_Dictionary')" > +[exportFilename]="getExportFilename('Data_Dictionary')" +[styleClass]="reportStyle" > diff --git a/src/app/components/table/table.component.scss b/src/app/components/table/table.component.scss index e69de29b..6639388c 100644 --- a/src/app/components/table/table.component.scss +++ b/src/app/components/table/table.component.scss @@ -0,0 +1,9 @@ +::ng-deep .default.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #343a40; + border-color: #454d55; + + & .p-icon { + color: #fff; + } +} \ No newline at end of file diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index ff66253c..eb88b8df 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -18,6 +18,7 @@ export class TableComponent implements OnInit { @Input() tableData: any[] = []; @Input() filterFields: any[] = []; @Input() buttonFilters: any[] = []; + @Input() reportStyle: string = 'default'; @ViewChild(Table) private dt: Table; diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html index dd2ec416..e9825063 100644 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html +++ b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html @@ -23,6 +23,6 @@

    - +
    \ No newline at end of file From 58766ba43f3fab89dbed671ccc6846ec6c3ab585 Mon Sep 17 00:00:00 2001 From: joshsjurik Date: Fri, 18 Oct 2024 16:16:49 -0400 Subject: [PATCH 17/20] Replaced all report tables with new PrimeNG table --- src/app/app-routing.module.ts | 10 +- src/app/app.module.ts | 5 +- src/app/common/table-classes.ts | 22 ++ src/app/components/table/table.component.html | 166 ++++---- src/app/components/table/table.component.scss | 141 ++++++- src/app/components/table/table.component.ts | 114 +++++- src/app/services/shared/shared.service.ts | 4 + .../capabilities/capabilities.component.html | 22 +- .../capabilities/capabilities.component.ts | 135 +------ .../organizations.component.html | 4 +- .../organizations/organizations.component.ts | 65 +--- .../website-service-category.component.html | 4 +- .../website-service-category.component.ts | 58 +-- .../data-dictionary-primeng.component.css | 0 .../data-dictionary-primeng.component.html | 28 -- .../data-dictionary-primeng.component.spec.ts | 25 -- .../data-dictionary-primeng.component.ts | 120 ------ .../data-dictionary.component.html | 2 +- .../data-dictionary.component.ts | 62 +-- .../glossary/glossary.component.html | 2 +- .../glossary/glossary.component.ts | 47 +-- .../fisma-pocs/fisma-pocs.component.html | 8 +- .../fisma-pocs/fisma-pocs.component.ts | 215 ++++------ .../views/security/fisma/fisma.component.html | 29 +- .../views/security/fisma/fisma.component.ts | 210 +++------- .../investments/investments.component.html | 21 +- .../investments/investments.component.ts | 366 ++++++------------ .../records-management.component.html | 21 +- .../records-management.component.ts | 124 ++---- .../systems/systems/systems.component.html | 48 +-- .../systems/systems/systems.component.ts | 354 +++++------------ .../views/systems/time/time.component.html | 4 +- src/app/views/systems/time/time.component.ts | 113 ++---- .../systems/websites/websites.component.html | 58 +-- .../systems/websites/websites.component.ts | 153 ++++---- .../it-standards/it-standards.component.html | 24 +- .../it-standards/it-standards.component.ts | 253 +++++------- tsconfig.json | 1 + 38 files changed, 1065 insertions(+), 1973 deletions(-) create mode 100644 src/app/common/table-classes.ts delete mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.css delete mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html delete mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts delete mode 100644 src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 83d207b3..cdd4cb7e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -44,7 +44,6 @@ import { Title } from '@angular/platform-browser'; import { FormsComponent } from './views/main/forms-glossary/forms/forms.component'; import { GlossaryComponent } from './views/main/forms-glossary/glossary/glossary.component'; import { DataDictionaryComponent } from './views/main/data-dictionary/data-dictionary.component'; -import { DataDictionaryPrimeNGComponent } from './views/main/data-dictionary-primeng/data-dictionary-primeng.component'; const routes: Routes = [ { path: '', component: HomeComponent, title: 'Home' }, @@ -205,14 +204,7 @@ const routes: Routes = [ component: DataDictionaryComponent, title: 'Data Dictionary', }, - - { - path: 'data_dictionary_primeng', - component: DataDictionaryPrimeNGComponent, - title: 'Data Dictionary - PrimeNG POC', - }, - - + { // Catch-all Redirect to Home path: '**', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7923f29a..91048813 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -70,6 +70,8 @@ import { ItStandardManagerComponent } from './components/manager-modals/it-stand import { EAViewComponent } from './views/architecture/ea-view/ea-view.component'; import { GearModelComponent } from './views/architecture/gear-model/gear-model.component'; +import { DataDictionaryComponent } from './views/main/data-dictionary/data-dictionary.component'; + // Global Variables import { Globals } from './common/globals'; @@ -79,7 +81,6 @@ import { BannerComponent } from './components/banner/banner.component'; import { IdentifierComponent } from './components/identifier/identifier.component'; import { SidebarComponent } from './components/sidebar/sidebar.component'; import { AccordionModule } from 'primeng/accordion'; -import { DataDictionaryPrimeNGComponent } from './views/main/data-dictionary-primeng/data-dictionary-primeng.component'; // PrimeNG Modules import { TableModule } from 'primeng/table'; @@ -136,7 +137,7 @@ import { InputTextModule } from 'primeng/inputtext'; BannerComponent, IdentifierComponent, SidebarComponent, - DataDictionaryPrimeNGComponent, + DataDictionaryComponent, TableComponent ], bootstrap: [AppComponent], imports: [AppRoutingModule, diff --git a/src/app/common/table-classes.ts b/src/app/common/table-classes.ts new file mode 100644 index 00000000..591016b5 --- /dev/null +++ b/src/app/common/table-classes.ts @@ -0,0 +1,22 @@ +export type TwoDimArray = T[][]; + +export interface Column { + header: string, + field: string, + isSortable?: boolean, + showColumn?: boolean, + class?: string, + formatter?: Function, + titleTooltip?: string +} + +export interface ExportColumn { + title: string; + dataKey: string; +} + +export interface ButtonFilter { + field: string, + filterBtnText: string, + filterOn: string +} \ No newline at end of file diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 52775837..312c84f9 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -1,77 +1,99 @@ - - {{filter}} - - -Reset - +
    + +
    + Filter Buttons: + + {{filter.filterBtnText}} + + Reset +
    + + +
    + - + [columns]="tableCols" + [value]="tableData" + [rowsPerPageOptions]="[10, 25, 50]" + [rows]="10" + [paginator]="isPaginated" + [scrollable]="true" + [scrollHeight]="screenHeight" + [exportHeader]="'customExportHeader'" + [exportFilename]="getExportFilename()" + [styleClass]="reportStyle" + selectionMode="single" + (onRowSelect)="onRowSelect($event)"> + + + +
    + + + + + + + + + + + + + + + + + +
    +
    -
    - - - - - - - - - - - - -
    -
    - - - -
    - {{ col.header }} - - - -
    - - - - - - - - -
    - - - -
    - {{ rowData[col.field] }} - {{ rowData[col.field] }} -
    - - -
    + + + + + + +
    + {{ col.header }} + {{ col.header }} + + + +
    + +
    + + + + + + + + + +
    + + + + + + {{ rowData[col.field] }} + + + + +
    diff --git a/src/app/components/table/table.component.scss b/src/app/components/table/table.component.scss index 6639388c..c28812ae 100644 --- a/src/app/components/table/table.component.scss +++ b/src/app/components/table/table.component.scss @@ -1,9 +1,140 @@ -::ng-deep .default.p-datatable .p-datatable-thead > tr > th { - color: #fff; - background-color: #343a40; - border-color: #454d55; +::ng-deep { + .p-datatable { + font-family: Nunito Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol; + font-size: .875rem; + font-weight: 400; + line-height: 1.5; + } + + .p-datatable .p-datatable-header { + border: none; + } + + .default.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #343a40; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + .it-strategy.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #14883c ; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + .business-systems.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #ce4844; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + .gsa-enterprise.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #2b60de; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + .security.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #aa6708; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + .technologies.p-datatable .p-datatable-thead > tr > th { + color: #fff; + background-color: #008080; + border-color: #454d55; + + & .p-icon { + color: #fff; + } + } + + + .p-datatable .p-datatable-tbody > tr { + color: #fff; + background-color: #343a40; + + & td { + color: #fff; + border-color: #343a40; + padding: 1.75rem 1rem; + } + + &:nth-of-type(odd) { + background-color: #41484e; + + & td { + border-color: #41484e; + padding: 1.75rem 1rem; + } + } + + & a { + color: #fff; + } + } + - & .p-icon { + .p-button-contrast { color: #fff; + background-color: #1a1a1a; + border-color: #1a1a1a; + + &:hover, &:active { + background-color: #070707; + border-color: #010000; + } + } + + .p-button-primary { + color: #fff; + background-color: #027eb2; + border-color: #027eb2; + + &:hover, &:active { + background-color: #1a82ae; + border-color: #1a82ae; + } + } + + .p-multiselect { + color: #fff; + background-color: #027eb2; + border-color: #027eb2; + border-radius: 0; + height: 42px; + top: 5px; + + &:hover { + background-color: #1a82ae; + border-color: #187aa3; + } + + & .p-multiselect-trigger { + color: #fff; + background-color: #027eb2; + border-color: #027eb2; + } } } \ No newline at end of file diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index eb88b8df..a982c496 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -1,10 +1,10 @@ -import { Component, HostListener, Input, OnInit, ViewChild } from '@angular/core'; -import { Table } from 'primeng/table'; +import { Component, EventEmitter, HostListener, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { Table, TableRowSelectEvent } from 'primeng/table'; +import { Column, ExportColumn, TwoDimArray, ButtonFilter } from '../../common/table-classes'; +import { SharedService } from '@services/shared/shared.service'; +import { TableService } from '@services/tables/table.service'; +import { ApiService } from '@services/apis/api.service'; -interface ExportColumn { - title: string; - dataKey: string; -} @Component({ selector: 'app-table', @@ -14,39 +14,58 @@ interface ExportColumn { export class TableComponent implements OnInit { - @Input() tableCols: any[] = []; + // Table columns and their options + @Input() tableCols: Column[] = []; + + // The table data @Input() tableData: any[] = []; - @Input() filterFields: any[] = []; - @Input() buttonFilters: any[] = []; + + // Two dimenstional array of button filters + // Each array of strings is a grouping of buttons + @Input() buttonFilters: TwoDimArray = []; + + // Report style that drives the overall color of the table @Input() reportStyle: string = 'default'; + // Website type for modal click fn + @Input() tableType: string = ''; + + // The name of report for the export csv + @Input() exportName: string = ''; + + // Filter event (some reports change available columns when filtered) + @Output() filterEvent = new EventEmitter(); + @ViewChild(Table) private dt: Table; @HostListener('window:resize', ['$event']) onResize(event) { - console.log(event.srcElement.innerHeight); - this.screenHeight = `${(event.srcElement.innerHeight - 500)}px`; + this.screenHeight = `${(event.srcElement.innerHeight - 400)}px`; } - visibleColumns: any[] = []; + visibleColumns: Column[] = []; isPaginated: boolean = true; exportColumns!: ExportColumn[]; currentButtonFilter: string = ''; screenHeight: string = ''; showFilters: boolean = false; - constructor() { - this.screenHeight = `${(window.innerHeight - 500)}px`; + constructor(public sharedService: SharedService, public tableService: TableService, public apiService: ApiService) { + this.screenHeight = `${(window.innerHeight - 400)}px`; } ngOnInit(): void { this.exportColumns = this.tableCols.map((col) => ({ title: col.header, dataKey: col.field })); this.tableCols.map(c => { - if(c.showColumn) { + if(this.showColumn(c)) { this.visibleColumns.push(c); } }) + + this.tableData.map(d => { + + }) } toggleVisible(e: any) { @@ -65,7 +84,7 @@ export class TableComponent implements OnInit { this.showFilters = !this.showFilters; } - getExportFilename(reportName: string) { + getExportFilename() { let today = new Date(); let year = today.getFullYear(); let month = today.toLocaleString('default', { month: 'long' }); @@ -75,17 +94,23 @@ export class TableComponent implements OnInit { let formattedDate = `${month}_${day}_${year}-${hour}_${mins}`; - return `GEAR_${reportName}-${formattedDate}`; + return `GEAR_${this.exportName}-${formattedDate}`; } - onButtonFilter(value: string) { - this.dt.filterGlobal(value, 'contains'); - this.currentButtonFilter = value; + onButtonFilter(filter: ButtonFilter) { + if(filter && filter.filterOn) { + this.dt.filter(filter.filterOn, filter.field, 'contains') + } + + this.filterEvent.emit(filter.filterBtnText); + + this.currentButtonFilter = filter.filterBtnText; } onButtonFilterClear() { this.dt.reset(); this.currentButtonFilter = ''; + this.filterEvent.emit(''); } applyFilteredStyle(filter: string) { @@ -96,4 +121,53 @@ export class TableComponent implements OnInit { return ''; } + showColumn(c: Column) { + return c.showColumn || !('showColumn' in c); + } + + onRowSelect(e: TableRowSelectEvent) { + switch (this.tableType) { + case 'investments': + this.tableService.investTableClick(e.data); + break; + case 'capabilities': + this.tableService.capsTableClick(e.data); + break; + case 'websiteServiceCategory': + this.tableService.websiteServiceCategoryTableClick(e.data); + break; + case 'organizations': + this.tableService.orgsTableClick(e.data); + break; + case 'website': + this.tableService.websitesTableClick(e.data); + break; + case 'records': + this.tableService.recordsTableClick(e.data); + break; + case 'time': + this.apiService.getOneSys(e.data['System Id']) + .subscribe((data: any[]) => { + this.tableService.systemsTableClick(data[0]); + }); + + // Change URL to include ID + this.sharedService.addIDtoURL(e.data, 'System Id'); + case 'systems': + this.tableService.systemsTableClick(e.data); + break; + case 'fisma': + this.tableService.fismaTableClick(e.data); + break; + case 'fismaPoc': + this.tableService.fismaTableClick(e.data); + break; + case 'itStandards': + this.tableService.itStandTableClick(e.data); + default: + console.log('no type'); + break; + } + } + } diff --git a/src/app/services/shared/shared.service.ts b/src/app/services/shared/shared.service.ts index 49ef5b47..3b66dce9 100644 --- a/src/app/services/shared/shared.service.ts +++ b/src/app/services/shared/shared.service.ts @@ -262,6 +262,10 @@ export class SharedService { if (value) return `Link`; }; + public linkFormatter(value) { + if (value) return `${value}`; + } + //// Date public dateFormatter(value, row, index, field) { const date = new Date(value); diff --git a/src/app/views/enterprise/capabilities/capabilities.component.html b/src/app/views/enterprise/capabilities/capabilities.component.html index 8c052bd6..0b9e42ee 100644 --- a/src/app/views/enterprise/capabilities/capabilities.component.html +++ b/src/app/views/enterprise/capabilities/capabilities.component.html @@ -41,7 +41,6 @@

    \ No newline at end of file diff --git a/src/app/views/enterprise/capabilities/capabilities.component.ts b/src/app/views/enterprise/capabilities/capabilities.component.ts index 3bf89c61..82afe194 100644 --- a/src/app/views/enterprise/capabilities/capabilities.component.ts +++ b/src/app/views/enterprise/capabilities/capabilities.component.ts @@ -7,6 +7,8 @@ import { ModalsService } from '@services/modals/modals.service'; import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { Column } from '../../../common/table-classes'; +import { Capability } from '@api/models/capabilities.model'; // Declare jQuery symbol declare var $: any; @@ -34,90 +36,34 @@ export class CapabilitiesComponent implements OnInit { this.modalService.currentCap.subscribe((row) => (this.row = row)); } - // Capabilities Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'CapTable', - classes: 'table-hover table-dark clickable-table fixed-table', - showColumns: false, - showExport: true, - exportFileName: 'GSA_Business_Capabilities', - headerStyle: 'bg-royal-blue', - pagination: true, - search: true, - sortName: 'ReferenceNum', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.capUrl, - }); + tableData: Capability[] = []; - // Capabilities Table Columns - capColumnDefs: any[] = [ + tableCols: Column[] = [ { field: 'ReferenceNum', - title: 'Ref Id', - sortable: true, + header: 'Ref Id', + isSortable: true, }, { field: 'Name', - title: 'Capability Name', - sortable: true, + header: 'Capability Name', + isSortable: true, }, { field: 'Description', - title: 'Description', - sortable: true, - class: 'text-truncate', + header: 'Description', + isSortable: true, + formatter: this.sharedService.formatDescription }, { field: 'Level', - title: 'Level', - sortable: true, + header: 'Level', + isSortable: true, }, { field: 'Parent', - title: 'Parent', - sortable: true, - }, - ]; - - // Capabilities by SSO Table Columns - ssoColumnDefs: any[] = [ - { - field: 'ReferenceNum', - title: 'Ref Id', - sortable: true, - }, - { - field: 'Name', - title: 'Capability Name', - sortable: true, - }, - { - field: 'Description', - title: 'Description', - sortable: true, - class: 'text-truncate', - }, - { - field: 'Level', - title: 'Level', - sortable: true, - }, - { - field: 'ParentCap', - title: 'Parent', - sortable: true, - }, - { - field: 'Organizations', - title: 'SSO', - sortable: true, - }, - { - field: 'Applications', - title: 'Apps', - sortable: true, + header: 'Parent', + isSortable: true, }, ]; @@ -130,23 +76,7 @@ export class CapabilitiesComponent implements OnInit { // Set JWT when logged into GEAR Manager when returning from secureAuth this.sharedService.setJWTonLogIn(); - $('#capTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.capColumnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the capabilities table - $('#capTable').on('click-row.bs.table', function (e, row) { - self.tableService.capsTableClick(row); - }.bind(this)); - - //Enable table sticky header - self.sharedService.enableStickyHeader("capTable"); - }); + this.apiService.getCapabilities().subscribe(c => this.tableData = c); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { @@ -161,37 +91,4 @@ export class CapabilitiesComponent implements OnInit { } }); } - - // Update table, filtering by SSO - changeCapSSO(sso: string) { - this.ssoTable = true; // SSO filters are on, expose main table button - - $('#capTable').bootstrapTable('refreshOptions', { - columns: this.ssoColumnDefs, - idTable: 'advSearchCapSSOTable', - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Business_Capabilities_by_SSO' - ), - }, - url: this.apiService.capUrl + '/sso/' + sso, - }); - - this.filterTitle = `${sso} `; - } - - backToMainCap() { - this.ssoTable = false; // Hide main button - - $('#capTable').bootstrapTable('refreshOptions', { - columns: this.capColumnDefs, - idTable: 'advSearchCapTable', - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_Business_Capabilities'), - }, - url: this.apiService.capUrl, - }); - - this.filterTitle = ''; - } } diff --git a/src/app/views/enterprise/organizations/organizations.component.html b/src/app/views/enterprise/organizations/organizations.component.html index ecbd4c13..4b3a2eb0 100644 --- a/src/app/views/enterprise/organizations/organizations.component.html +++ b/src/app/views/enterprise/organizations/organizations.component.html @@ -27,8 +27,8 @@

    -
    +
    -
    +
    \ No newline at end of file diff --git a/src/app/views/enterprise/organizations/organizations.component.ts b/src/app/views/enterprise/organizations/organizations.component.ts index a9f30318..b76eab02 100644 --- a/src/app/views/enterprise/organizations/organizations.component.ts +++ b/src/app/views/enterprise/organizations/organizations.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit } from '@angular/core'; -import { Location } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; import { ModalsService } from '@services/modals/modals.service'; -import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { Column } from '../../../common/table-classes'; +import { Organization } from '@api/models/organizations.model'; // Declare jQuery symbol declare var $: any; @@ -21,60 +21,41 @@ export class OrganizationsComponent implements OnInit { constructor( private apiService: ApiService, - private location: Location, private modalService: ModalsService, private route: ActivatedRoute, - private router: Router, - private sharedService: SharedService, private tableService: TableService, private titleService: Title ) { this.modalService.currentInvest.subscribe((row) => (this.row = row)); } - // Organizations Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'OrgTable', - classes: 'table-hover table-dark clickable-table', - showColumns: false, - showExport: true, - exportFileName: 'GSA_Organizations', - headerStyle: 'bg-royal-blue', - pagination: true, - search: true, - sortName: 'OrgSymbol', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.orgUrl, - }); + tableData: Organization[] = []; - // Organizations Table Columns - columnDefs: any[] = [ + tableCols: Column[] = [ { field: 'OrgSymbol', - title: 'Org Symbol', - sortable: true, + header: 'Org Symbol', + isSortable: true, }, { field: 'Name', - title: 'Organization Name', - sortable: true, + header: 'Organization Name', + isSortable: true, }, { field: 'SSOName', - title: 'SSO Name', - sortable: true, + header: 'SSO Name', + isSortable: true, }, { field: 'TwoLetterOrgSymbol', - title: 'Two Letter Org', - sortable: true, + header: 'Two Letter Org', + isSortable: true, }, { field: 'TwoLetterOrgName', - title: 'Two Letter Org Name', - sortable: true, + header: 'Two Letter Org Name', + isSortable: true, }, ]; @@ -84,23 +65,7 @@ export class OrganizationsComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#orgTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the organization table - $('#orgTable').on('click-row.bs.table', function (e, row) { - this.tableService.orgsTableClick(row); - }.bind(this)); - - //Enable table sticky header - self.sharedService.enableStickyHeader("orgTable"); - }); + this.apiService.getOrganizations().subscribe(o => this.tableData = o); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { diff --git a/src/app/views/enterprise/website-service-category/website-service-category.component.html b/src/app/views/enterprise/website-service-category/website-service-category.component.html index 2580bb1a..2d42091e 100644 --- a/src/app/views/enterprise/website-service-category/website-service-category.component.html +++ b/src/app/views/enterprise/website-service-category/website-service-category.component.html @@ -35,7 +35,7 @@

    -
    +
    @@ -57,7 +57,7 @@

    -
    +

    diff --git a/src/app/views/enterprise/website-service-category/website-service-category.component.ts b/src/app/views/enterprise/website-service-category/website-service-category.component.ts index 7bb0d6a0..d6c073e2 100644 --- a/src/app/views/enterprise/website-service-category/website-service-category.component.ts +++ b/src/app/views/enterprise/website-service-category/website-service-category.component.ts @@ -1,12 +1,13 @@ import { Component, OnInit } from '@angular/core'; -import { Location } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; import { ModalsService } from '@services/modals/modals.service'; import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { Column } from '../../../common/table-classes'; +import { Service_Category } from '@api/models/service-category.model'; // Declare jQuery symbol declare var $: any; @@ -21,10 +22,8 @@ export class WebsiteServiceCategoryComponent implements OnInit { constructor( private apiService: ApiService, - private location: Location, private modalService: ModalsService, private route: ActivatedRoute, - private router: Router, private sharedService: SharedService, private tableService: TableService, private titleService: Title @@ -34,41 +33,23 @@ export class WebsiteServiceCategoryComponent implements OnInit { ); } - // websiteServiceCategory Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'websiteServiceCategoryTable', - classes: 'table-hover table-dark clickable-table', - showColumns: false, - showExport: true, - exportFileName: 'GSA_websiteServiceCategory', - exportIgnoreColumn:[], - headerStyle: 'bg-royal-blue', - pagination: true, - search: true, - sortName: 'name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.websiteServiceCategoryUrl, - }); + tableData: Service_Category[] = []; - // websiteServiceCategory Table Columns - columnDefs: any[] = [ + tableCols: Column[] = [ { field: 'website_service_category_id', - title: 'Id', - sortable: true, + header: 'Id', + isSortable: true, }, { field: 'name', - title: 'Name', - sortable: true, + header: 'Name', + isSortable: true, }, { field: 'description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, formatter: this.sharedService.formatDescription } ]; @@ -79,22 +60,7 @@ export class WebsiteServiceCategoryComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#websiteServiceCategoryTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the serviceCategory table - $('#websiteServiceCategoryTable').on('click-row.bs.table', function (e, row) { - this.tableService.websiteServiceCategoryTableClick(row); - }.bind(this)); - //Enable table sticky header - self.sharedService.enableStickyHeader("websiteServiceCategoryTable"); - }); + this.apiService.getWebsiteServiceCategory().subscribe(w => this.tableData = w); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.css b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html deleted file mode 100644 index e9825063..00000000 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
    -
    -

    - - - Data Dictionary - -

    - - - More Help - -
    - -
    - -
    -
    \ No newline at end of file diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts deleted file mode 100644 index 3d9e7b26..00000000 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { DataDictionaryPrimeNGComponent } from './data-dictionary-primeng.component'; - -describe('DataDictionaryPrimeNGComponent', () => { - let component: DataDictionaryPrimeNGComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ DataDictionaryPrimeNGComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DataDictionaryPrimeNGComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts b/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts deleted file mode 100644 index f0fb1b06..00000000 --- a/src/app/views/main/data-dictionary-primeng/data-dictionary-primeng.component.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataDictionary } from '@api/models/data-dictionary.model'; -import { ApiService } from '@services/apis/api.service'; - -import { SharedService } from '@services/shared/shared.service'; -import { TableService } from '@services/tables/table.service'; - -// Declare jQuery symbol -declare var $: any; - -@Component({ - selector: 'data-dictionary-primeng', - templateUrl: './data-dictionary-primeng.component.html', - styleUrls: ['./data-dictionary-primeng.component.css'] -}) -export class DataDictionaryPrimeNGComponent implements OnInit { - - testData: DataDictionary[] = []; - - testFilterFields: any[] = ['ReportName', 'Term', 'TermDefinition', 'DefinitionSource']; - - buttonFilters: any[] = []; - - testCols = [ - { - field: 'ReportName', - header: 'Report Name', - isSortable: true, - showColumn: true - }, - { - field: 'Term', - header: 'Term', - isSortable: true, - showColumn: true - }, - { - field: 'TermDefinition', - header: 'Definition', - isSortable: false, - showColumn: true - }, - { - field: 'DefinitionSource', - header: 'Definition Source', - isSortable: true, - showColumn: true - }, - { - field: 'DefinitionSourceLink', - header: 'Definition Source Link', - isSortable: false, - showColumn: false, - isLink: true - }, - { - field: 'DataSource', - header: 'Data Source', - isSortable: true, - showColumn: true - }, - { - field: 'DataSourceLink', - header: 'Data Source Link', - isSortable: false, - showColumn: false - } -]; - - row: Object = {}; - - constructor( - private sharedService: SharedService, - private tableService: TableService, - private apiService: ApiService) {} - - ngOnInit(): void { - this.apiService.getEntireDataDictionary().subscribe(defs => { - this.testData = defs; - - const testEntry: DataDictionary = { - ReportName: 'IT Investments', - Term: 'Investment UII', - TermDefinition: 'A unique investment identifier', - DefinitionSource: '', - DefinitionSourceLink: '', - DataSource: 'Folio', - DataSourceLink: '' - } - - this.testData.push(testEntry); - - this.createButtonFilters(this.testData, 'ReportName'); - }); - - // Enable popovers - $(function () { - $('[data-toggle="popover"]').popover() - }) - } - - // TODO: Make this more generic and move it into a service - // TODO: Create arrays based on passed in property names - /* - * General Idea: the user passes in what property names they want to make filters of - * then we use those here to create seperate arrays of all unique values - * pass those arrays through a filter to remove any empty strings - * then push them all into a parent array that gets sent into the table component - * this lets us do groupings without having to pass in a bunch of "loose" data - * */ - createButtonFilters(data: any, propName: string) { - const uniqueReportNames = [...new Set(data.map(item => item.ReportName))]; - // let uniqueDefinitionSource = [...new Set(data.map(item => item.DefinitionSource))] as DataDictionary[]; - // uniqueDefinitionSource = uniqueDefinitionSource.filter(u => u); - - this.buttonFilters.push(uniqueReportNames); - // this.buttonFilters.push(uniqueDefinitionSource); - console.log(this.buttonFilters) - } -} diff --git a/src/app/views/main/data-dictionary/data-dictionary.component.html b/src/app/views/main/data-dictionary/data-dictionary.component.html index 07b25da3..79065645 100644 --- a/src/app/views/main/data-dictionary/data-dictionary.component.html +++ b/src/app/views/main/data-dictionary/data-dictionary.component.html @@ -25,7 +25,7 @@

    Data Dictionary

    -
    +

    \ No newline at end of file diff --git a/src/app/views/main/data-dictionary/data-dictionary.component.ts b/src/app/views/main/data-dictionary/data-dictionary.component.ts index 2596a178..53a2b471 100644 --- a/src/app/views/main/data-dictionary/data-dictionary.component.ts +++ b/src/app/views/main/data-dictionary/data-dictionary.component.ts @@ -2,7 +2,9 @@ import { Component, OnInit } from '@angular/core'; import { ApiService } from '@services/apis/api.service'; import { SharedService } from '@services/shared/shared.service'; -import { TableService } from '@services/tables/table.service'; + +import { Column } from '../../../common/table-classes'; +import { DataDictionary } from '@api/models/data-dictionary.model'; // Declare jQuery symbol declare var $: any; @@ -18,82 +20,58 @@ export class DataDictionaryComponent implements OnInit { constructor( private sharedService: SharedService, - private tableService: TableService, private apiService: ApiService) {} - // Data Dictionary Table Options - ddTableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: false, - idTable: null, - classes: "table-hover table-dark table-responsive", - showColumns: true, - showExport: true, - exportFileName: 'GEAR_Data_Dictionary', - headerStyle: null, - pagination: true, - search: true, - sortName: 'Term', - sortOrder: 'asc', - showToggle: true - }); + tableData: DataDictionary[] = []; - // Data Dictionary Table Columns - ddColumnDefs: any[] = [{ + tableCols: Column[] = [ + { field: 'ReportName', - title: 'Report Name', - sortable: true + header: 'Report Name', + isSortable: true }, { field: 'Term', - title: 'Term', - sortable: true + header: 'Term', + isSortable: true }, { field: 'TermDefinition', - title: 'Definition' + header: 'Definition' }, { field: 'DefinitionSource', - title: 'Definition Source', - sortable: true, + header: 'Definition Source', + isSortable: true, class: 'table-column-break-word' }, { field: 'DefinitionSourceLink', - title: 'Definition Source Link', + header: 'Definition Source Link', formatter: this.sharedService.linksFormatter, - visible: false + showColumn: false }, { field: 'DataSource', - title: 'Data Source', - sortable: true + header: 'Data Source', + isSortable: true }, { field: 'DataSourceLink', - title: 'Data Source Link', + header: 'Data Source Link', formatter: this.sharedService.linksFormatter, - visible: false + isSortable: false } ]; ngOnInit(): void { this.apiService.getEntireDataDictionary().subscribe(defs => { - $('#dataDictionaryTable').bootstrapTable($.extend(this.ddTableOptions, { - columns: this.ddColumnDefs, - data: defs, - })); + this.tableData = defs; }); // Enable popovers $(function () { $('[data-toggle="popover"]').popover() }) - - const self = this; - $(document).ready(function() { - //Enable table sticky header - self.sharedService.enableStickyHeader("dataDictionaryTable"); - }); } } diff --git a/src/app/views/main/forms-glossary/glossary/glossary.component.html b/src/app/views/main/forms-glossary/glossary/glossary.component.html index bedc985a..a0e1dc06 100644 --- a/src/app/views/main/forms-glossary/glossary/glossary.component.html +++ b/src/app/views/main/forms-glossary/glossary/glossary.component.html @@ -25,7 +25,7 @@

    Glossary

    -
    +

    \ No newline at end of file diff --git a/src/app/views/main/forms-glossary/glossary/glossary.component.ts b/src/app/views/main/forms-glossary/glossary/glossary.component.ts index 66ff133d..5fb664c0 100644 --- a/src/app/views/main/forms-glossary/glossary/glossary.component.ts +++ b/src/app/views/main/forms-glossary/glossary/glossary.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { Column } from '@common/table-classes'; -import { SharedService } from '@services/shared/shared.service'; -import { TableService } from '@services/tables/table.service'; +import * as glossaryData from '../../../../../assets/statics/glossary.json'; // Declare jQuery symbol declare var $: any; @@ -15,40 +15,23 @@ export class GlossaryComponent implements OnInit { row: Object = {}; - constructor( - private sharedService: SharedService, - private tableService: TableService) { } + constructor() { } - // Glossary Table Options - glossTableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: false, - idTable: null, - classes: "table-hover table-dark", - showColumns: false, - showExport: true, - exportFileName: 'GEAR_Glossary', - headerStyle: null, - pagination: true, - search: true, - sortName: 'Term', - sortOrder: 'asc', - showToggle: true, - url: '/assets/statics/glossary.json' - }); + tableData: any[] = []; // Glossary Table Columns - glossColumnDefs: any[] = [{ + tableCols: Column[] = [{ field: 'Term', - title: 'Term', - sortable: true + header: 'Term', + isSortable: true }, { field: 'Definition', - title: 'Definition' + header: 'Definition' }, { field: 'Reference', - title: 'Reference' + header: 'Reference' }] ngOnInit(): void { @@ -57,15 +40,7 @@ export class GlossaryComponent implements OnInit { $('[data-toggle="popover"]').popover() }) - $('#glossaryTable').bootstrapTable($.extend(this.glossTableOptions, { - columns: this.glossColumnDefs, - data: [], - })); - - const self = this; - $(document).ready(function() { - //Enable table sticky header - self.sharedService.enableStickyHeader("glossaryTable"); - }); + let rawData = JSON.stringify(glossaryData); + this.tableData = JSON.parse(rawData).default; } } diff --git a/src/app/views/security/fisma-pocs/fisma-pocs.component.html b/src/app/views/security/fisma-pocs/fisma-pocs.component.html index f856d57b..265edb78 100644 --- a/src/app/views/security/fisma-pocs/fisma-pocs.component.html +++ b/src/app/views/security/fisma-pocs/fisma-pocs.component.html @@ -30,12 +30,8 @@

    -
    -
    -
    - +
    -
    - +
    \ No newline at end of file diff --git a/src/app/views/security/fisma-pocs/fisma-pocs.component.ts b/src/app/views/security/fisma-pocs/fisma-pocs.component.ts index ea9377ec..c82d443f 100644 --- a/src/app/views/security/fisma-pocs/fisma-pocs.component.ts +++ b/src/app/views/security/fisma-pocs/fisma-pocs.component.ts @@ -3,9 +3,10 @@ import { ActivatedRoute } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; import { ModalsService } from '@services/modals/modals.service'; -import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { Column } from '../../../common/table-classes'; +import { FISMA } from '@api/models/fisma.model'; // Declare jQuery symbol declare var $: any; @@ -22,77 +23,60 @@ export class FismaPocsComponent implements OnInit { private apiService: ApiService, private modalService: ModalsService, private route: ActivatedRoute, - private sharedService: SharedService, private tableService: TableService, private titleService: Title ) { this.modalService.currentFismaSys.subscribe((row) => (this.row = row)); } - // FISMA POC Table Options - pocTableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'FismaPOCTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_FISMA_POCs', - headerStyle: 'bg-warning', - pagination: true, - search: true, - sortName: 'Name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.fismaUrl, - }); - - // FISMA POC Table Columns - pocColumnDefs: any[] = [ + tableData: FISMA[] = []; + + tableCols: Column[] = [ { field: 'Name', - title: 'System Name', - sortable: true, + header: 'System Name', + isSortable: true, }, { field: 'FIPS_Impact_Level', - title: 'FIPS Impact Level', - sortable: true, + header: 'FIPS Impact Level', + isSortable: true, }, { - field: 'Authorizing Official', - title: 'Authorizing Official', - sortable: true, + field: 'AO', + header: 'Authorizing Official', + isSortable: true, formatter: this.pocFormatter, }, { - field: 'System Owner', - title: 'System Owner', - sortable: true, + field: 'SO', + header: 'System Owner', + isSortable: true, formatter: this.pocFormatter, }, { field: 'ISSM', - title: 'ISSM', - sortable: true, + header: 'ISSM', + isSortable: true, formatter: this.pocFormatter, }, { field: 'ISSO', - title: 'ISSO', - sortable: true, + header: 'ISSO', + isSortable: true, formatter: this.pocFormatter, }, { field: 'RespOrg', - title: 'Responsible Org', - sortable: true, - visible: false, + header: 'Responsible Org', + isSortable: true, + showColumn: false, }, { field: 'BusOrg', - title: 'Business Org', - sortable: true, - visible: false, + header: 'Business Org', + isSortable: true, + showColumn: false, }, ]; @@ -102,45 +86,7 @@ export class FismaPocsComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#fismaPOCTable').bootstrapTable( - $.extend(this.pocTableOptions, { - columns: this.pocColumnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Filter out "Pending" Status - $('#fismaPOCTable').bootstrapTable('filterBy', { - Status: 'Active', - SystemLevel: 'System', - Reportable: 'Yes', - }); - - // Method to handle click events on the FISMA POC table - $('#fismaPOCTable').on( - 'click-row.bs.table', - function (e, row, $element, field) { - if (! - ( - field === 'Authorizing Official' - || - field === 'System Owner' - || - field === 'ISSM' - || - field === 'ISSO' - ) - ) { - this.tableService.fismaTableClick(row); - } - }.bind(this) - ); - - //Enable table sticky header - self.sharedService.enableStickyHeader("fismaPOCTable"); - }); + this.apiService.getFISMA().subscribe(f => this.tableData = f); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { @@ -158,74 +104,49 @@ export class FismaPocsComponent implements OnInit { }); } - pocFormatter(value, row, index, field) { - const p = row.POC; - let poc = null; - let poc1 = null; - let pocs = []; - - // Split by POC Type - if (p) { - poc1 = p.split('*'); - - // For every POC Type - for (let index = 0; index < poc1.length; index++) { - var poctype = poc1[index]; - // Split if multiple POCs in same type - poctype = poctype.split(':'); - - // Only continue for POC type matching the desired field - if (poctype[0] === field) { - poc = poctype[1].split('; '); - - // Return if there are no POCs in this field - if (poc[0] === '') { - return 'None Provided'; - } else { - // For every POC - for (var i = 0; i < poc.length; i++) { - // Split the different components - let pieces = poc[i].split(','); - - let tmpObj = { - name: pieces[0], - phone: pieces[2], - email: pieces[1], - }; - - let linkStr = null; - - // Only continue if name exists - if (tmpObj.name) { - linkStr = tmpObj.name + '
    '; - - // Format email into a HTML link - if (tmpObj.email) { - linkStr += `${tmpObj.email}
    `; - } - - // Format number into phone format - if (tmpObj.phone) { - linkStr += - tmpObj.phone.substring(0, 4) + - '-' + - tmpObj.phone.substring(4, 7) + - '-' + - tmpObj.phone.substring(7, 11) + - '
    '; - } - - pocs.push(linkStr); - } - } - } - } - } - // Block each POC's info with breaks - return pocs.join('

    '); - } else { + pocFormatter(value) { + // remove beginning field type from poc info + let pocsCleanedUp = value.split(':'); + // split poc groupings into array + let pocs: string[] = pocsCleanedUp[1].split(';'); + // the final string that gets displayed + let finalDisplayStr = ''; + + // if there's no pocs display a default + if(pocs.length === 0 || pocs[0] === "") { return 'None Provided'; } + + // iterate over all poc groupings + pocs.map(p => { + if(p !== " ") { + // split the poc group into specific contact types + let contactTypes = p.split(','); + let name = contactTypes[0]; + let email = contactTypes[1]; + let phone = contactTypes[2]; + + // temp display string + let displayStr = ''; + + if(name) { + displayStr += `${name}
    `; + } + + if(email) { + displayStr += `${email}
    `; + } + + if(phone) { + displayStr += `${phone.substring(0, 4)}-${phone.substring(4, 7)}-${phone.substring(7, 11)}
    `; + } + + // append the temp display string to the final display string + finalDisplayStr += `${displayStr}
    `; + } + }); + + return finalDisplayStr; } } diff --git a/src/app/views/security/fisma/fisma.component.html b/src/app/views/security/fisma/fisma.component.html index 8e5dd23c..4b85f740 100644 --- a/src/app/views/security/fisma/fisma.component.html +++ b/src/app/views/security/fisma/fisma.component.html @@ -30,27 +30,14 @@

    -
    -
    +
    + + - FISMA Definition - - - -
    - Filter Buttons: - - - -
    -
    - - -
    -
    + data-trigger="focus" data-placement="auto" data-html="true" title="" data-content="A FISMA system represents a logical boundary for which common security controls and policies are applied to a grouping of related assets (e.g., networks, devices, and people) to accomplish some mission or business objective. It is informed by security artifacts, such as a system security and privacy plan (SSPP). (Link)"> + FISMA Definition + +
    \ No newline at end of file diff --git a/src/app/views/security/fisma/fisma.component.ts b/src/app/views/security/fisma/fisma.component.ts index cb8fcfa3..235d1c14 100644 --- a/src/app/views/security/fisma/fisma.component.ts +++ b/src/app/views/security/fisma/fisma.component.ts @@ -1,12 +1,13 @@ import { Component, OnInit } from '@angular/core'; -import { Location } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; import { ModalsService } from '@services/modals/modals.service'; import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { ButtonFilter, Column, TwoDimArray } from '../../../common/table-classes'; +import { FISMA } from '@api/models/fisma.model'; // Declare jQuery symbol declare var $: any; @@ -22,10 +23,8 @@ export class FismaComponent implements OnInit { constructor( private apiService: ApiService, - private location: Location, private modalService: ModalsService, private route: ActivatedRoute, - private router: Router, private sharedService: SharedService, private tableService: TableService, private titleService: Title @@ -33,129 +32,109 @@ export class FismaComponent implements OnInit { this.modalService.currentFismaSys.subscribe((row) => (this.row = row)); } - // FISMA System Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'FismaTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_FISMA_Systems_Inventory', - exportIgnoreColumn:[], - headerStyle: 'bg-warning', - pagination: true, - search: true, - sortName: 'Name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.fismaUrl, - }); + tableData: FISMA[] = []; - // FISMA System Table Columns - columnDefs: any[] = [ - /* { - field: 'DisplayName', - title: 'Alias/Acronym', - sortable: true - }, */ { + buttonFilters: TwoDimArray = [ + [ + { field: 'Status', filterBtnText: 'Retired Fisma Systems', filterOn: 'Inactive' } + ] + ]; + + tableCols: Column[] = [ + { field: 'ID', - title: 'ID', - sortable: true, - visible: false, + header: 'ID', + isSortable: true, + showColumn: false, }, { field: 'Name', - title: 'System Name', - sortable: true, + header: 'System Name', + isSortable: true, }, { field: 'Status', - title: 'Status', - sortable: true, - visible: false, + header: 'Status', + isSortable: true, + showColumn: false, }, { field: 'ATODate', - title: 'ATO Date', - sortable: true, + header: 'ATO Date', + isSortable: true, formatter: this.sharedService.dateFormatter, }, { field: 'RenewalDate', - title: 'Renewal Date', - sortable: true, + header: 'Renewal Date', + isSortable: true, formatter: this.sharedService.dateFormatter, }, { field: 'ATOType', - title: 'ATO Type', - sortable: true, + header: 'ATO Type', + isSortable: true, }, { field: 'FIPS_Impact_Level', - title: 'FIPS Impact Level', - sortable: true, + header: 'FIPS Impact Level', + isSortable: true, }, { field: 'RelatedArtifacts', - title: 'Related Artifacts', - sortable: true, + header: 'Related Artifacts', + isSortable: true, formatter: this.sharedService.relArtifactsFormatter, }, { field: 'Description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, + showColumn: true, formatter: this.sharedService.formatDescription }, { field: 'ParentName', - title: 'Parent System', - sortable: true, - visible: false, + header: 'Parent System', + isSortable: true, + showColumn: false, }, { field: 'Reportable', - title: 'FISMA Reportable', - sortable: true, - visible: false, + header: 'FISMA Reportable', + isSortable: true, + showColumn: false, }, { field: 'PII', - title: 'PII', - sortable: true, - visible: false, + header: 'PII', + isSortable: true, + showColumn: false, }, { field: 'CUI', - title: 'CUI', - sortable: true, - visible: false, + header: 'CUI', + isSortable: true, + showColumn: false, }, { field: 'FedContractorLoc', - title: 'Fed or Contractor System', - sortable: true, - visible: false, + header: 'Fed or Contractor System', + isSortable: true, + showColumn: false, }, { field: 'RespOrg', - title: 'Responsible Org', - sortable: true, - visible: false, + header: 'Responsible Org', + isSortable: true, + showColumn: false, }, { field: 'ServiceType', - title: 'Cloud Service Type', - sortable: true, - visible: false, - }, /* , { - field: 'BusOrg', - title: 'Business Org', - sortable: true, - visible: false - } ,*/ + header: 'Cloud Service Type', + isSortable: true, + showColumn: false, + } ]; ngOnInit(): void { @@ -164,35 +143,7 @@ export class FismaComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#fismaTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Filter out "Pending" Status - $('#fismaTable').bootstrapTable('filterBy', { - Status: 'Active', - SystemLevel: 'System', - Reportable: 'Yes', - }); - - // Method to handle click events on the FISMA Systems table - $('#fismaTable').on( - 'click-row.bs.table', - function (e, row, $element, field) { - if (field !== 'RelatedArtifacts' ) { - this.tableService.fismaTableClick(row); - } - }.bind(this) - ); - - //Enable table sticky header - self.sharedService.enableStickyHeader("fismaTable"); - }); + this.apiService.getFISMA().subscribe(f => this.tableData = f); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { @@ -209,53 +160,4 @@ export class FismaComponent implements OnInit { } }); } - - // Update table to Retire Systems - showRetired() { - this.sharedService.disableStickyHeader("fismaTable"); - this.retiredTable = true; // Expose main table button after "Retired" button is pressed - - this.columnDefs.push({ - field: 'InactiveDate', - title: 'Inactive Date', - sortable: true, - formatter: this.sharedService.dateFormatter, - }); - - // Change columns, filename, and url - $('#fismaTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_Retired_FISMA_Systems'), - }, - }); - - // Filter to only "Inactive" Status - $('#fismaTable').bootstrapTable('filterBy', { - Status: ['Inactive'], - }); - this.sharedService.enableStickyHeader("fismaTable"); - } - - backToMainFisma() { - this.sharedService.disableStickyHeader("fismaTable"); - this.retiredTable = false; // Hide main button - - // Change back to default - this.columnDefs.pop(); - $('#fismaTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_FISMA_Systems_Inventory'), - }, - }); - - // Filter back to "Active" Status - $('#fismaTable').bootstrapTable('filterBy', { - Status: 'Active', - SystemLevel: 'System', - Reportable: 'Yes', - }); - this.sharedService.enableStickyHeader("fismaTable"); - } } diff --git a/src/app/views/strategy/investments/investments.component.html b/src/app/views/strategy/investments/investments.component.html index 0b09ded1..bf46eb2e 100644 --- a/src/app/views/strategy/investments/investments.component.html +++ b/src/app/views/strategy/investments/investments.component.html @@ -62,26 +62,7 @@

    > -
    - -
    - Filter Buttons: - - - - - - - -
    -
    - -
    + \ No newline at end of file diff --git a/src/app/views/strategy/investments/investments.component.ts b/src/app/views/strategy/investments/investments.component.ts index e869fc66..cfd90b05 100644 --- a/src/app/views/strategy/investments/investments.component.ts +++ b/src/app/views/strategy/investments/investments.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { Location } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; import { ModalsService } from '@services/modals/modals.service'; @@ -9,6 +8,7 @@ import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; import { Investment } from '@api/models/investments.model'; +import { ButtonFilter, Column, TwoDimArray } from '../../../common/table-classes'; // Declare jQuery symbol declare var $: any; @@ -33,10 +33,8 @@ export class InvestmentsComponent implements OnInit { constructor( private apiService: ApiService, - private location: Location, private modalService: ModalsService, private route: ActivatedRoute, - private router: Router, public sharedService: SharedService, private tableService: TableService, private titleService: Title @@ -44,236 +42,230 @@ export class InvestmentsComponent implements OnInit { this.modalService.currentInvest.subscribe((row) => (this.row = row)); } - // Investment Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'InvestTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_IT_Investments', - exportIgnoreColumn:[], - headerStyle: 'bg-success', - pagination: true, - search: true, - sortName: 'Name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.investUrl, - }); + tableData: Investment[] = []; - // Investments Main Table Columns - columnDefs: any[] = [ + buttonFilters: TwoDimArray = [ + [ + { field: 'Status', filterBtnText: 'Eliminated', filterOn: 'eliminated' }, + { field: '', filterBtnText: 'Previous Year $', filterOn: '' }, + { field: '', filterBtnText: 'Current Year $', filterOn: '' }, + { field: '', filterBtnText: 'Budget Year $', filterOn: '' } + ] + ]; + + tableCols: Column[] = []; + + defaultCols: Column[] = [ { field: 'Name', - title: 'Investment Name', - sortable: true, + header: 'Investment Name', + isSortable: true, }, { field: 'Description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, + showColumn: true, formatter: this.sharedService.formatDescription }, { field: 'Type', - title: 'Type', - sortable: true, + header: 'Type', + isSortable: true, }, { field: 'IT_Portfolio', - title: 'Part of IT Portfolio', - sortable: true, + header: 'Part of IT Portfolio', + isSortable: true, }, { field: 'Budget_Year', - title: 'Budget Year', - sortable: true, + header: 'Budget Year', + isSortable: true, }, { field: 'InvManager', - title: 'Investment Manager', - sortable: true, + header: 'Investment Manager', + isSortable: true, formatter: this.sharedService.noneProvidedFormatter, }, { field: 'Status', - title: 'Status', - sortable: true, + header: 'Status', + isSortable: true, }, { field: 'Start_Year', - title: 'Start Year', - sortable: true, - visible: false, + header: 'Start Year', + isSortable: true, + showColumn: false, }, { field: 'End_Year', - title: 'End Year', - sortable: true, - visible: false, + header: 'End Year', + isSortable: true, + showColumn: false, }, { field: 'PSA', - title: 'Primary Service Area', - sortable: true, - visible: false, + header: 'Primary Service Area', + isSortable: true, + showColumn: false, }, { field: 'Cloud_Alt', - title: 'Cloud Alt. Evaluation', - sortable: true, - visible: false, + header: 'Cloud Alt. Evaluation', + isSortable: true, + showColumn: false, }, { field: 'Comments', - title: 'Comments', - sortable: true, - visible: false, + header: 'Comments', + isSortable: true, + showColumn: false, }, { field: 'UII', - title: 'Investment UII', - sortable: true, - visible: false, + header: 'Investment UII', + isSortable: true, + showColumn: false, }, { field: 'Updated_Date', - title: 'Updated Date', - sortable: true, - visible: false, + header: 'Updated Date', + isSortable: true, + showColumn: false, formatter: this.sharedService.dateFormatter, }, ]; // Previous Year Investments Table Columns - PYcolumnDefs: any[] = [ + PYcolumnDefs: Column[] = [ { field: 'UII', - title: 'Investment UII', - sortable: true, - visible: false, + header: 'Investment UII', + isSortable: true, + showColumn: false, }, { field: 'Name', - title: 'Investment Name', - sortable: true, + header: 'Investment Name', + isSortable: true, }, { field: 'Total_Spend_PY', - title: 'Total IT Spending ($ M): PY', - sortable: true, + header: 'Total IT Spending ($ M): PY', + isSortable: true, }, { field: 'DME_Agency_Fund_PY', - title: 'DME Agency Funding ($ M): PY', - sortable: true, + header: 'DME Agency Funding ($ M): PY', + isSortable: true, }, { field: 'DME_Contributions_PY', - title: 'DME Contributions ($ M): PY', - sortable: true, + header: 'DME Contributions ($ M): PY', + isSortable: true, }, { field: 'OnM_Agency_Fund_PY', - title: 'O&M Agency Funding ($ M): PY', - sortable: true, + header: 'O&M Agency Funding ($ M): PY', + isSortable: true, }, { field: 'OnM_Contributions_PY', - title: 'O&M Contributions ($ M): PY', - sortable: true, + header: 'O&M Contributions ($ M): PY', + isSortable: true, }, ]; // Current Year Investments Table Columns - CYcolumnDefs: any[] = [ + CYcolumnDefs: Column[] = [ { field: 'UII', - title: 'Investment UII', - sortable: true, - visible: false, + header: 'Investment UII', + isSortable: true, + showColumn: false, }, { field: 'Name', - title: 'Investment Name', - sortable: true, + header: 'Investment Name', + isSortable: true, }, { field: 'Total_Spend_CY', - title: 'Total IT Spending ($ M): CY', - sortable: true, + header: 'Total IT Spending ($ M): CY', + isSortable: true, }, { field: 'DME_Agency_Fund_CY', - title: 'DME Agency Funding ($ M): CY', - sortable: true, + header: 'DME Agency Funding ($ M): CY', + isSortable: true, }, { field: 'DME_Contributions_CY', - title: 'DME Contributions ($ M): CY', - sortable: true, + header: 'DME Contributions ($ M): CY', + isSortable: true, }, { field: 'OnM_Agency_Fund_CY', - title: 'O&M Agency Funding ($ M): CY', - sortable: true, + header: 'O&M Agency Funding ($ M): CY', + isSortable: true, }, { field: 'OnM_Contributions_CY', - title: 'O&M Contributions ($ M): CY', - sortable: true, + header: 'O&M Contributions ($ M): CY', + isSortable: true, }, ]; // Budget Year Investments Table Columns - BYcolumnDefs: any[] = [ + BYcolumnDefs: Column[] = [ { field: 'UII', - title: 'Investment UII', - sortable: true, - visible: false, + header: 'Investment UII', + isSortable: true, + showColumn: false, }, { field: 'Name', - title: 'Investment Name', - sortable: true, + header: 'Investment Name', + isSortable: true, }, { field: 'Total_Spend_BY', - title: 'Total IT Spending ($ M): BY', - sortable: true, + header: 'Total IT Spending ($ M): BY', + isSortable: true, }, { field: 'DME_Agency_Fund_BY', - title: 'DME Agency Funding ($ M): BY', - sortable: true, + header: 'DME Agency Funding ($ M): BY', + isSortable: true, }, { field: 'DME_Contributions_BY', - title: 'DME Contributions ($ M): BY', - sortable: true, + header: 'DME Contributions ($ M): BY', + isSortable: true, }, { field: 'DME_Budget_Auth_BY', - title: 'DME Budget Authority Agency Funding ($ M): BY', - sortable: true, + header: 'DME Budget Authority Agency Funding ($ M): BY', + isSortable: true, }, { field: 'OnM_Agency_Fund_BY', - title: 'O&M Agency Funding ($ M): BY', - sortable: true, + header: 'O&M Agency Funding ($ M): BY', + isSortable: true, }, { field: 'OnM_Contributions_BY', - title: 'O&M Contributions ($ M): BY', - sortable: true, + header: 'O&M Contributions ($ M): BY', + isSortable: true, }, { field: 'OnM_Budget_Auth_BY', - title: 'O&M Budget Authority Agency Funding ($ M): BY', - sortable: true, + header: 'O&M Budget Authority Agency Funding ($ M): BY', + isSortable: true, }, ]; @@ -283,31 +275,9 @@ export class InvestmentsComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#investTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Filter to only non-eliminated investments - $('#investTable').bootstrapTable('filterBy', { - Status: this.nonEliminatedTypes, - }); - - // Method to handle click events on the Investments table - $('#investTable').on( - 'click-row.bs.table', - function (e, row) { - this.tableService.investTableClick(row); - }.bind(this) - ); + this.tableCols = this.defaultCols; - //Enable table sticky header - self.sharedService.enableStickyHeader("investTable"); - }); + this.apiService.getInvestments().subscribe(i => this.tableData = i); // Get Investment data for visuals this.apiService.getInvestments().subscribe((data: any[]) => { @@ -350,6 +320,30 @@ export class InvestmentsComponent implements OnInit { }); } + onFilterEvent(filter: string) { + if(filter === 'Eliminated') { + this.tableCols = this.defaultCols; + // Hide visualization + $('#investViz').collapse('hide'); + } else if(filter === 'Previous Year $') { + this.tableCols = this.PYcolumnDefs; + // Hide visualization + $('#investViz').collapse('hide'); + } else if(filter === 'Current Year $') { + this.tableCols = this.CYcolumnDefs; + // Hide visualization + $('#investViz').collapse('hide'); + } else if(filter === 'Budget Year $') { + this.tableCols === this.BYcolumnDefs; + // Hide visualization + $('#investViz').collapse('hide'); + } else { + this.tableCols = this.defaultCols; + // Show visualization + $('#investViz').collapse('show'); + } + } + getAriaLabel(data: { name: string, value: number }[]): string { const total = data.reduce((acc, cur) => acc + cur.value, 0); if (data.length === 1) { @@ -360,108 +354,6 @@ export class InvestmentsComponent implements OnInit { } } - // Update table from filter buttons - eliminatedFilter() { - this.sharedService.disableStickyHeader("investTable"); - this.filteredTable = true; // Filters are on, expose main table button - this.filterTitle = 'Eliminated'; - - // Hide visualization when on eliminated items - $('#investViz').collapse('hide'); - - $('#investTable').bootstrapTable('filterBy', { - Status: this.eliminatedTypes, - }); - $('#investTable').bootstrapTable('refreshOptions', { - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Eliminated_IT_Investments' - ), - }, - }); - this.sharedService.enableStickyHeader("investTable"); - } - - previousYearFilter() { - this.sharedService.disableStickyHeader("investTable"); - this.filteredTable = true; // Filters are on, expose main table button - this.filterTitle = 'Previous Year'; - - // Hide visualization - $('#investViz').collapse('hide'); - - $('#investTable').bootstrapTable('filterBy', {}); - $('#investTable').bootstrapTable('refreshOptions', { - columns: this.PYcolumnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Previous_Year_IT_Investments' - ), - }, - }); - this.sharedService.enableStickyHeader("investTable"); - } - - currentYearFilter() { - this.sharedService.disableStickyHeader("investTable"); - this.filteredTable = true; // Filters are on, expose main table button - this.filterTitle = 'Current Year'; - - // Hide visualization when on eliminated items - $('#investViz').collapse('hide'); - - $('#investTable').bootstrapTable('filterBy', {}); - $('#investTable').bootstrapTable('refreshOptions', { - columns: this.CYcolumnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Current_Year_IT_Investments' - ), - }, - }); - this.sharedService.enableStickyHeader("investTable"); - } - - budgetYearFilter() { - this.sharedService.disableStickyHeader("investTable"); - this.filteredTable = true; // Filters are on, expose main table button - this.filterTitle = 'Budget Year'; - - // Hide visualization - $('#investViz').collapse('hide'); - - $('#investTable').bootstrapTable('filterBy', {}); - $('#investTable').bootstrapTable('refreshOptions', { - columns: this.BYcolumnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Budget_Year_IT_Investments' - ), - }, - }); - this.sharedService.enableStickyHeader("investTable"); - } - - backToMainInvest() { - this.sharedService.disableStickyHeader("investTable"); - this.filteredTable = false; // Hide main button - this.filterTitle = ''; - - $('#investViz').collapse('show'); - - // Remove filters and back to default - $('#investTable').bootstrapTable('filterBy', { - Status: this.nonEliminatedTypes, - }); - $('#investTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_IT_Investments'), - }, - }); - this.sharedService.enableStickyHeader("investTable"); - } - onSelect(chartData): void { this.sharedService.disableStickyHeader("investTable"); this.filteredTable = true; // Filters are on, expose main table button diff --git a/src/app/views/systems/records-management/records-management.component.html b/src/app/views/systems/records-management/records-management.component.html index 63ee5e68..31e78847 100644 --- a/src/app/views/systems/records-management/records-management.component.html +++ b/src/app/views/systems/records-management/records-management.component.html @@ -39,26 +39,7 @@

    - - - -
    +
    \ No newline at end of file diff --git a/src/app/views/systems/records-management/records-management.component.ts b/src/app/views/systems/records-management/records-management.component.ts index 54840626..9225e68f 100644 --- a/src/app/views/systems/records-management/records-management.component.ts +++ b/src/app/views/systems/records-management/records-management.component.ts @@ -1,15 +1,16 @@ import { Component, OnInit } from '@angular/core'; -import { Location } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { ApiService } from '@services/apis/api.service'; -import { ModalsService } from '@services/modals/modals.service'; import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; import { trigger, state, style, animate, transition } from '@angular/animations'; +import { Column } from '../../../common/table-classes'; +import { Record } from '@api/models/records.model'; + // Declare jQuery symbol declare var $: any; @@ -34,115 +35,95 @@ export class RecordsManagementComponent implements OnInit { constructor( private apiService: ApiService, - private location: Location, - private modalService: ModalsService, private route: ActivatedRoute, - private router: Router, public sharedService: SharedService, private tableService: TableService, private titleService: Title ) {} - // Records Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'RecordsTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_Record_Schedules', - exportIgnoreColumn:[], - headerStyle: 'bg-danger', - pagination: true, - search: true, - sortName: 'GSA_Number', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.recordsUrl, - }); + tableData: Record[] = []; - // Apps Table Columns - columnDefs: any[] = [ + tableCols: Column[] = [ { field: 'GSA_Number', - title: 'GSA Number', - sortable: true, + header: 'GSA Number', + isSortable: true, }, { field: 'Record_Item_Title', - title: 'Record Title', - sortable: true, + header: 'Record Title', + isSortable: true, }, { field: 'Description', - title: 'Description', - sortable: false, - visible: true, + header: 'Description', + isSortable: false, + showColumn: true, formatter: this.sharedService.formatDescription }, { field: 'Record_Status', - title: 'Status', - visible: false, - sortable: true, + header: 'Status', + showColumn: false, + isSortable: true, }, { field: 'RG', - title: 'Record Group', - visible: false, - sortable: true, + header: 'Record Group', + showColumn: false, + isSortable: true, }, { field: 'Retention_Instructions', - title: 'Retention Instructions', - sortable: false, - visible: false, + header: 'Retention Instructions', + isSortable: false, + showColumn: false, class: 'text-truncate', }, { field: 'Legal_Disposition_Authority', - title: 'Disposition Authority (DA)', - sortable: true, + header: 'Disposition Authority (DA)', + isSortable: true, }, { field: 'Type_Disposition', - title: 'Disposition Type', - visible: false, - sortable: true, + header: 'Disposition Type', + showColumn: false, + isSortable: true, }, { field: 'Date_DA_Approved', - title: 'DA Approval Date', - visible: false, - sortable: true, + header: 'DA Approval Date', + showColumn: false, + isSortable: true, }, { field: 'Disposition_Notes', - title: 'Disposition Notes', - sortable: false, - visible: false, + header: 'Disposition Notes', + isSortable: false, + showColumn: false, class: 'text-truncate', }, { field: 'FP_Category', - title: 'FP Category', - visible: false, - sortable: true, + header: 'FP Category', + showColumn: false, + isSortable: true, }, { field: 'PII', - title: 'PII', - sortable: true, + header: 'PII', + isSortable: true, }, { field: 'CUI', - title: 'CUI', - sortable: true, + header: 'CUI', + isSortable: true, }, { field: 'FY_Retention_Years', - title: 'Retention Years', - sortable: true, + header: 'Retention Years', + isSortable: true, }, ]; @@ -155,26 +136,7 @@ export class RecordsManagementComponent implements OnInit { // Set JWT when logged into GEAR Manager when returning from secureAuth this.sharedService.setJWTonLogIn(); - $('#recordsTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the Records table - $('#recordsTable').on( - 'click-row.bs.table', - function (e, row) { - this.tableService.recordsTableClick(row); - }.bind(this) - ); - - //Enable table sticky header - self.sharedService.enableStickyHeader("recordsTable"); - }); + this.apiService.getRecords().subscribe(r => this.tableData = r); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { diff --git a/src/app/views/systems/systems/systems.component.html b/src/app/views/systems/systems/systems.component.html index e32c7515..438ada23 100644 --- a/src/app/views/systems/systems/systems.component.html +++ b/src/app/views/systems/systems/systems.component.html @@ -60,14 +60,7 @@

    - diff --git a/src/app/views/systems/systems/systems.component.ts b/src/app/views/systems/systems/systems.component.ts index 30a4113c..f383f8f3 100644 --- a/src/app/views/systems/systems/systems.component.ts +++ b/src/app/views/systems/systems/systems.component.ts @@ -9,6 +9,7 @@ import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; import { System } from '@api/models/systems.model'; +import { ButtonFilter, Column, TwoDimArray } from '../../../common/table-classes'; // Declare D3 & Sankey library declare var d3: any; @@ -73,181 +74,191 @@ export class SystemsComponent implements OnInit { url: this.apiService.sysUrl, }); - // Systems Table Columns - columnDefs: any[] = [ + tableData: System[] = []; + + buttonFilters: TwoDimArray = [ + [ + { field: 'CloudYN', filterBtnText: 'Cloud Enabled', filterOn: 'Yes' }, + { field: 'Status', filterBtnText: 'Inactive', filterOn: 'Inactive' } + ] + ]; + + tableCols: Column[] = []; + + defaultTableCols: Column[] = [ { field: 'ID', - title: 'ID', - sortable: true, - visible: false, + header: 'ID', + isSortable: true, + showColumn: false, }, { field: 'DisplayName', - title: 'Alias/Acronym', - sortable: true, + header: 'Alias/Acronym', + isSortable: true, }, { field: 'Name', - title: 'System Name', - sortable: true, + header: 'System Name', + isSortable: true, }, { field: 'Description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, + showColumn: true, formatter: this.sharedService.formatDescription }, { field: 'SystemLevel', - title: 'System Level', - sortable: true, + header: 'System Level', + isSortable: true, }, { field: 'Status', - title: 'Status', - sortable: true, + header: 'Status', + isSortable: true, }, { field: 'RespOrg', - title: 'Responsible Org', - sortable: true, + header: 'Responsible Org', + isSortable: true, }, { field: 'BusOrgSymbolAndName', - title: 'SSO/CXO', - sortable: true, + header: 'SSO/CXO', + isSortable: true, }, { field: 'BusOrg', - title: 'Business Org', - sortable: true, + header: 'Business Org', + isSortable: true, }, { field: 'ParentName', - title: 'Parent System', - sortable: true, - visible: false, + header: 'Parent System', + isSortable: true, + showColumn: false, }, { field: 'CSP', - title: 'Hosting Provider', - sortable: true, - visible: false, + header: 'Hosting Provider', + isSortable: true, + showColumn: false, }, { field: 'CloudYN', - title: 'Cloud Hosted?', - sortable: true, - visible: false, + header: 'Cloud Hosted?', + isSortable: true, + showColumn: false, }, { field: 'ServiceType', - title: 'Cloud Service Type', - sortable: true, - visible: false, + header: 'Cloud Service Type', + isSortable: true, + showColumn: false, }, { field: 'AO', - title: 'Authorizing Official', - sortable: true, - visible: false, + header: 'Authorizing Official', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'SO', - title: 'System Owner', - sortable: true, - visible: false, + header: 'System Owner', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'BusPOC', - title: 'Business POC', - sortable: true, - visible: false, + header: 'Business POC', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'TechPOC', - title: 'Technical POC', - sortable: true, - visible: false, + header: 'Technical POC', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'DataSteward', - title: 'Data Steward', - sortable: true, - visible: false, + header: 'Data Steward', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, ]; // Inactive Column Defs - inactiveColumnDefs: any[] = [ + inactiveColumnDefs: Column[] = [ { field: 'Name', - title: 'System Name', - sortable: true, + header: 'System Name', + isSortable: true, }, { field: 'Description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, + showColumn: true, formatter: this.sharedService.formatDescription }, { field: 'SystemLevel', - title: 'System Level', - sortable: true, + header: 'System Level', + isSortable: true, }, { field: 'Status', - title: 'Status', - sortable: true, + header: 'Status', + isSortable: true, }, { field: 'RespOrg', - title: 'Responsible Org', - sortable: true, + header: 'Responsible Org', + isSortable: true, }, { field: 'BusOrg', - title: 'Business Org', - sortable: true, + header: 'Business Org', + isSortable: true, }, { field: 'CSP', - title: 'Cloud Server Provider', - sortable: true, - visible: false, + header: 'Cloud Server Provider', + isSortable: true, + showColumn: false, }, { field: 'CloudYN', - title: 'Cloud Hosted?', - sortable: true, - visible: false, + header: 'Cloud Hosted?', + isSortable: true, + showColumn: false, }, { field: 'AO', - title: 'Authorizing Official', - sortable: true, - visible: false, + header: 'Authorizing Official', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'SO', - title: 'System Owner', - sortable: true, - visible: false, + header: 'System Owner', + isSortable: true, + showColumn: false, formatter: this.sharedService.pocStringNameFormatter, }, { field: 'InactiveDate', - title: 'Inactive Date', - sortable: true, + header: 'Inactive Date', + isSortable: true, formatter: this.sharedService.dateFormatter, }, ]; @@ -261,33 +272,9 @@ export class SystemsComponent implements OnInit { // Set JWT when logged into GEAR Manager when returning from secureAuth this.sharedService.setJWTonLogIn(); - $('#systemTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Filter to only active systems - $('#systemTable').bootstrapTable('filterBy', { - Status: 'Active', - BusApp: 'Yes', - }); - - // Method to handle click events on the Systems table - $('#systemTable').on( - 'click-row.bs.table', - function (e, row) { - this.tableService.systemsTableClick(row); - // this.getInterfaceData(row.ID); - }.bind(this) - ); + this.tableCols = this.defaultTableCols; - //Enable table sticky header - self.sharedService.enableStickyHeader("systemTable"); - }); + this.apiService.getSystems().subscribe(s => this.tableData = s); // Get System data for visuals this.apiService.getSystems().subscribe((data: any[]) => { @@ -318,6 +305,8 @@ export class SystemsComponent implements OnInit { // console.log(this.vizData); // Debug }); + + // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { var detailSysID = params['sysID']; @@ -333,6 +322,21 @@ export class SystemsComponent implements OnInit { }); } + onFilterEvent(filter: string) { + if(filter === 'Inactive') { + this.tableCols = this.inactiveColumnDefs; + // Hide visualization when on alternative filters + $('#sysViz').collapse('hide'); + } else if(filter === 'Cloud Enabled'){ + this.tableCols = this.defaultTableCols; + // Hide visualization when on alternative filters + $('#sysViz').collapse('hide'); + } else { + // Hide visualization when on alternative filters + $('#sysViz').collapse('show'); + } + } + getAriaLabel(data: { name: string, value: number }[]): string { const total = data.reduce((acc, cur) => acc + cur.value, 0); if (data.length === 1) { @@ -343,150 +347,6 @@ export class SystemsComponent implements OnInit { } } - // Update table from filter buttons if only filtering ONE column. Not currently used in business systems report. - changeFilter(field: string, term: string) { - this.sharedService.disableStickyHeader("systemTable"); - this.filteredTable = true; // Filters are on, expose main table button - var filter = {}; - filter[field] = term; - var title = ''; - var activeColDef = this.columnDefs; - var exportIgnoreColumn = this.activeExportIgnoreColumn; - - // Hide visualization when on alternative filters - $('#sysViz').collapse('hide'); - - $('#systemTable').bootstrapTable('filterBy', filter); - switch (field) { - case 'CloudYN': - title = 'Cloud Enabled GSA'; - break; - case 'Status': - title = term + ' GSA'; - activeColDef = this.inactiveColumnDefs; - exportIgnoreColumn = this.inactiveExportIgnoreColumn; - break; - } - $('#systemTable').bootstrapTable('refreshOptions', { - columns: activeColDef, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - `GSA_${title.replace(' ', '_')}_Systems` - ), - ignoreColumn: exportIgnoreColumn - }, - }); - this.filterTitle = title; - this.sharedService.enableStickyHeader("systemTable"); - } - - //The following is adapted from fisma.component.ts to filter on multiple columns of data rather than one - // Update table to Cloud Business Systems - showCloud() { - $('#systemTable').floatThead('destroy'); - this.filteredTable = true; // Expose main table button after "Cloud Enabled" button is pressed - this.filterTitle = 'Cloud GSA'; - - // Hide visualization when on alternative filters - $('#sysViz').collapse('hide'); - - // Change columns, filename, and url - $('#systemTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_Cloud_Business_Systems'), - ignoreColumn:this.activeExportIgnoreColumn - }, - }); - - // Filter to only "Cloud" Business Systems/Subsystems - $('#systemTable').bootstrapTable('filterBy', { - Status: ['Active'], - BusApp: 'Yes', - CloudYN: 'Yes', - }); - this.sharedService.enableStickyHeader("systemTable"); - } - - // Update table to Inactive Business Systems - showInactive() { - this.sharedService.disableStickyHeader("systemTable"); - this.filteredTable = true; // Expose main table button after "Inactive" button is pressed - this.filterTitle = 'Inactive GSA'; - - // Hide visualization when on alternative filters - $('#sysViz').collapse('hide'); - - // Change columns, filename, and url - $('#systemTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Inactive_Business_Systems' - ), - ignoreColumn: this.inactiveExportIgnoreColumn - }, - }); - - // Filter to only "Inactive" Business Systems/Subsystems - $('#systemTable').bootstrapTable('filterBy', { - Status: ['Inactive'], - BusApp: 'Yes', - }); - this.sharedService.enableStickyHeader("systemTable"); - } - - // Update table to Pending Business Systems - showPending() { - this.sharedService.disableStickyHeader("systemTable"); - this.filteredTable = true; // Expose main table button after "Pending" button is pressed - this.filterTitle = 'Pending GSA'; - - // Hide visualization when on alternative filters - $('#sysViz').collapse('hide'); - - // Change columns, filename, and url - $('#systemTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_Pending_Business_Systems' - ), - ignoreColumn: this.activeExportIgnoreColumn - }, - }); - - // Filter to only "Pending" Business Systems/Subsystems - $('#systemTable').bootstrapTable('filterBy', { - Status: ['Pending'], //, - //Commenting out BusApp: 'Yes' since pending systems need to be reviewed by EA and Security of whether they are a business system. - //BusApp: 'Yes' - }); - this.sharedService.enableStickyHeader("systemTable"); - } - //The preceding code is adapted from fisma.component.ts to filter on multiple columns of data rather than one - - backToMainSys() { - this.sharedService.disableStickyHeader("systemTable"); - this.filteredTable = false; // Hide main button - - $('#sysViz').collapse('show'); - - // Remove filters and back to default - $('#systemTable').bootstrapTable('filterBy', { - Status: 'Active', - BusApp: 'Yes', - }); - $('#systemTable').bootstrapTable('refreshOptions', { - columns: this.columnDefs, - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_Systems_SubSystems'), - ignoreColumn:this.activeExportIgnoreColumn - }, - }); - this.sharedService.enableStickyHeader("systemTable"); - } - onSelect(chartData): void { this.sharedService.disableStickyHeader("systemTable"); this.filteredTable = true; // Filters are on, expose main table button diff --git a/src/app/views/systems/time/time.component.html b/src/app/views/systems/time/time.component.html index f312e664..8a2f76af 100644 --- a/src/app/views/systems/time/time.component.html +++ b/src/app/views/systems/time/time.component.html @@ -61,6 +61,6 @@

    -
    - + + \ No newline at end of file diff --git a/src/app/views/systems/time/time.component.ts b/src/app/views/systems/time/time.component.ts index 60135c85..526344a9 100644 --- a/src/app/views/systems/time/time.component.ts +++ b/src/app/views/systems/time/time.component.ts @@ -10,6 +10,9 @@ import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { Column } from '../../../common/table-classes'; +import { TIME } from '@api/models/systime.model'; + // Declare jQuery symbol declare var $: any; @@ -54,89 +57,73 @@ export class TimeComponent implements OnInit { }); } - // TIME Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'TimeTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'Systems_TIME_Report', - headerStyle: 'bg-danger', - pagination: true, - search: true, - sortName: 'Name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.timeUrl, - }); - - // TIME Table Columns - columnDefs: any[] = [ + tableData: TIME[] = []; + + tableCols: Column[] = [ { field: 'System Name', - title: 'System Name', - sortable: true, + header: 'System Name', + isSortable: true, }, { field: 'FY', - title: 'FY', - sortable: true, + header: 'FY', + isSortable: true, }, { field: 'TIME Designation', - title: 'TIME Designation', - sortable: true, + header: 'TIME Designation', + isSortable: true, }, { field: 'Business Score', - title: 'Business Score', - visible: false, - sortable: true, + header: 'Business Score', + showColumn: false, + isSortable: true, }, { field: 'Technical Score', - title: 'Technical Score', - visible: false, - sortable: true, + header: 'Technical Score', + showColumn: false, + isSortable: true, }, { field: 'O&M Cost', - title: 'O&M Cost', - visible: false, - sortable: true, + header: 'O&M Cost', + showColumn: false, + isSortable: true, }, { field: 'DM&E Cost', - title: 'DM&E Cost', - visible: false, - sortable: true, + header: 'DM&E Cost', + showColumn: false, + isSortable: true, }, { field: 'Software/Hardware License Costs', - title: 'License Costs', - visible: false, - sortable: true, + header: 'License Costs', + showColumn: false, + isSortable: true, }, { field: 'Questionnaire Last Updated', - title: 'Questionnaire Last Updated', - sortable: true, - visible: false, + header: 'Questionnaire Last Updated', + isSortable: true, + showColumn: false, formatter: this.sharedService.dateFormatter, }, { field: 'POC Last Updated', - title: 'POC of Last Updated', - sortable: true, - visible: false, + header: 'POC of Last Updated', + isSortable: true, + showColumn: false, formatter: this.sharedService.emailFormatter, }, { field: 'File Link', - title: 'File Link', - sortable: true, - visible: false, + header: 'File Link', + isSortable: true, + showColumn: false, formatter: this.sharedService.linksFormatter, }, ]; @@ -147,33 +134,7 @@ export class TimeComponent implements OnInit { $('[data-toggle="popover"]').popover(); }); - $('#timeTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the Systems table - $('#timeTable').on('click-row.bs.table', function (e, row, $element, field) { - if (field !== 'File Link' ) { - // Grab data for system by name - this.apiService - .getOneSys(row['System Id']) - .subscribe((data: any[]) => { - this.tableService.systemsTableClick(data[0]); - }); - - // Change URL to include ID - this.sharedService.addIDtoURL(row, 'Id'); - } - }.bind(this)); - - //Enable table sticky header - self.sharedService.enableStickyHeader("timeTable"); - }); + this.apiService.getTIME().subscribe(t => this.tableData = t); // Visualization data this.apiService.getTIME().subscribe((data: any[]) => { diff --git a/src/app/views/systems/websites/websites.component.html b/src/app/views/systems/websites/websites.component.html index ad9beab7..0cea5c5f 100644 --- a/src/app/views/systems/websites/websites.component.html +++ b/src/app/views/systems/websites/websites.component.html @@ -57,55 +57,11 @@

    -
    - -
    - Filter Buttons: - - - - - - - +
    + + - ecxcli website scans."> Website definition -
    -
    -
    - -
    +
    diff --git a/src/app/views/systems/websites/websites.component.ts b/src/app/views/systems/websites/websites.component.ts index 59414891..4273c8fe 100644 --- a/src/app/views/systems/websites/websites.component.ts +++ b/src/app/views/systems/websites/websites.component.ts @@ -8,6 +8,9 @@ import { SharedService } from '@services/shared/shared.service'; import { TableService } from '@services/tables/table.service'; import { Title } from '@angular/platform-browser'; +import { ButtonFilter, Column, TwoDimArray } from '../../../common/table-classes'; +import { Website } from '@api/models/websites.model'; + // Declare jQuery symbol declare var $: any; @@ -30,132 +33,125 @@ export class WebsitesComponent implements OnInit { private titleService: Title ) {} - // Websites Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'WebsitesTable', - classes: 'table-hover table-dark clickable-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_Websites', - headerStyle: 'bg-danger', - pagination: true, - search: true, - sortName: 'domain', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.websitesUrl, - }); - - // Apps Table Columns - columnDefs: any[] = [ + tableData: Website[] = []; + + buttonFilters: TwoDimArray = [ + [ + { field: 'production_status', filterBtnText: 'Production', filterOn: 'production' }, + { field: 'production_status', filterBtnText: 'Decommissioned', filterOn: 'decommissioned' }, + { field: 'production_status', filterBtnText: 'Redirects', filterOn: 'redirect' }, + { field: 'production_status', filterBtnText: 'Staging', filterOn: 'staging' } + ] + ]; + + tableCols: Column[] = [ { field: 'domain', - title: 'Domain', - sortable: true, + header: 'Domain', + isSortable: true, }, { field: 'office', - title: 'Office', - sortable: true, + header: 'Office', + isSortable: true, }, { field: 'site_owner_email', - title: 'Website Manager', - sortable: true, + header: 'Website Manager', + isSortable: true, }, { field: 'contact_email', - title: 'Contact Email', - sortable: true, - visible: false, + header: 'Contact Email', + isSortable: true, + showColumn: false, }, { field: 'production_status', - title: 'Status', - sortable: true, + header: 'Status', + isSortable: true, }, { field: 'redirects_to', - title: 'Redirect URL', - sortable: true, - visible: false, + header: 'Redirect URL', + isSortable: true, + showColumn: false, }, { field: 'required_by_law_or_policy', - title: 'Required by Law/Policy?', - sortable: true, - visible: false, + header: 'Required by Law/Policy?', + isSortable: true, + showColumn: false, }, { field: 'has_dap', - title: 'DAP Enabled', - sortable: true, - visible: false, + header: 'DAP Enabled', + isSortable: true, + showColumn: false, }, { field: 'https', - title: 'HTTPS Enabled', - sortable: true, - visible: false, + header: 'HTTPS Enabled', + isSortable: true, + showColumn: false, }, { field: 'mobile_friendly', - title: 'Mobile Friendly?', - sortable: true, - visible: false, + header: 'Mobile Friendly?', + isSortable: true, + showColumn: false, }, { field: 'has_search', - title: 'Has Search?', - sortable: true, + header: 'Has Search?', + isSortable: true, }, { field: 'repository_url', - title: 'Repository URL', - sortable: true, - visible: false, + header: 'Repository URL', + isSortable: true, + showColumn: false, }, { field: 'hosting_platform', - title: 'Hosting Platform', - sortable: true, + header: 'Hosting Platform', + isSortable: true, }, { field: 'cms_platform', - title: 'Content Management Platform', - sortable: true, - visible: false, + header: 'Content Management Platform', + isSortable: true, + showColumn: false, }, { field: 'sub_office', - title: 'Sub-office', - sortable: false, - visible: false, + header: 'Sub-office', + isSortable: false, + showColumn: false, class: 'text-truncate', }, { field: 'type_of_site', - title: 'Type of Site', - sortable: true, - visible: true, + header: 'Type of Site', + isSortable: true, + showColumn: true, class: 'text-truncate', }, { field: 'digital_brand_category', - title: 'Digital Brand Category', - sortable: true, - visible: false, + header: 'Digital Brand Category', + isSortable: true, + showColumn: false, formatter: this.sharedService.formatDescription }, { field: 'target_decommission_date', - title: 'Target Decommission Date', - sortable: true, - visible: false, + header: 'Target Decommission Date', + isSortable: true, + showColumn: false, formatter: this.sharedService.utcDateFormatter }, - ]; +]; ngOnInit(): void { // Enable popovers @@ -166,26 +162,7 @@ export class WebsitesComponent implements OnInit { // Set JWT when logged into GEAR Manager when returning from secureAuth this.sharedService.setJWTonLogIn(); - $('#websitesTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); - - // Sets initial filtering of table - $(document).ready(this.resetTableFilters()); - - const self = this; - $(document).ready(() => { - // Method to handle click events on the Website table - $('#websitesTable').on('click-row.bs.table', function (e, row) { - this.tableService.websitesTableClick(row); - }.bind(this)); - - //Enable table sticky header - self.sharedService.enableStickyHeader("websitesTable"); - }); + this.apiService.getWebsites().subscribe(w => this.tableData = w); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { diff --git a/src/app/views/technologies/it-standards/it-standards.component.html b/src/app/views/technologies/it-standards/it-standards.component.html index d2cf6f34..17eff4f9 100644 --- a/src/app/views/technologies/it-standards/it-standards.component.html +++ b/src/app/views/technologies/it-standards/it-standards.component.html @@ -62,29 +62,11 @@

    IT Standards site"> IT-Standards definition - - -
    - Filter Buttons: - - - - - - - - -
    -
    + + + \ No newline at end of file diff --git a/src/app/views/technologies/it-standards/it-standards.component.ts b/src/app/views/technologies/it-standards/it-standards.component.ts index f76bb874..654e9159 100644 --- a/src/app/views/technologies/it-standards/it-standards.component.ts +++ b/src/app/views/technologies/it-standards/it-standards.component.ts @@ -10,6 +10,7 @@ import { Title } from '@angular/platform-browser'; import { ITStandards } from '@api/models/it-standards.model'; import { DataDictionary } from '@api/models/data-dictionary.model'; +import { ButtonFilter, Column, TwoDimArray } from '@common/table-classes'; // Declare jQuery symbol declare var $: any; @@ -25,6 +26,7 @@ export class ItStandardsComponent implements OnInit { filterTitle: string = ''; attrDefinitions: DataDictionary[] = []; columnDefs: any[] = []; + dataReady: boolean = false; constructor( private apiService: ApiService, @@ -38,24 +40,22 @@ export class ItStandardsComponent implements OnInit { ) { this.modalService.currentITStand.subscribe((row) => (this.row = row)); } + + tableData: ITStandards[] = []; - // IT Standard Table Options - tableOptions: {} = this.tableService.createTableOptions({ - advancedSearch: true, - idTable: 'ITStandardTable', - classes: 'table-hover table-dark clickable-table fixed-table', - showColumns: true, - showExport: true, - exportFileName: 'GSA_IT_Standards', - exportIgnoreColumn:[], - headerStyle: 'bg-teal', - pagination: true, - search: true, - sortName: 'Name', - sortOrder: 'asc', - showToggle: true, - url: this.apiService.techUrl, - }); + buttonFilters: TwoDimArray = [ + [ + { field: 'DeploymentType', filterBtnText: 'Desktop', filterOn: 'desktop' }, + { field: 'DeploymentType', filterBtnText: 'Server', filterOn: 'server' } + ], + [ + { field: 'Status', filterBtnText: 'Approved', filterOn: 'approved' }, + { field: 'Status', filterBtnText: 'Denied', filterOn: 'denied' }, + { field: 'Status', filterBtnText: 'Retired', filterOn: 'retired' } + ] + ]; + + tableCols: Column[] = []; YesNo(value, row, index, field) { return value === 'T'? "Yes" : "No"; @@ -70,186 +70,181 @@ export class ItStandardsComponent implements OnInit { this.attrDefinitions = defs // IT Standard Table Columns - this.columnDefs = [{ + this.tableCols = [{ field: 'ID', - title: 'ID', - sortable: true, - visible: false, + header: 'ID', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('ID') }, { field: 'Name', - title: 'IT Standard Name', - sortable: true, + header: 'IT Standard Name', + isSortable: true, titleTooltip: this.getTooltip('IT Standard Name') }, { field: 'Manufacturer', - title: 'Manufacturer ID', - sortable: true, - visible: false, + header: 'Manufacturer ID', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Manufacturer ID') }, { field: 'ManufacturerName', - title: 'Manufacturer', - sortable: true, + header: 'Manufacturer', + isSortable: true, titleTooltip: this.getTooltip('Manufacturer Name') }, { field: 'SoftwareProduct', - title: 'Product ID', - sortable: true, - visible: false, + header: 'Product ID', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Product ID') }, { field: 'SoftwareProductName', - title: 'Product', - sortable: true, - visible: false, + header: 'Product', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Product Name') }, { field: 'SoftwareVersion', - title: 'Version ID', - sortable: true, - visible: false, + header: 'Version ID', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Version ID') }, { field: 'SoftwareVersionName', - title: 'Version', - sortable: true, - visible: false, + header: 'Version', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Version Name') }, { field: 'SoftwareRelease', - title: 'Release ID', - sortable: true, - visible: false, + header: 'Release ID', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Release ID') }, { field: 'SoftwareReleaseName', - title: 'Release', - sortable: true, - visible: false, + header: 'Release', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Software Release Name') }, { field: 'EndOfLifeDate', - title: 'Vendor End of Life Date', - sortable: true, - visible: false, + header: 'Vendor End of Life Date', + isSortable: true, + showColumn: false, formatter: this.sharedService.dateFormatter, titleTooltip: this.getTooltip('Software End of Life Date') }, { field: 'OldName', - title: 'Also Known As', - sortable: true, - visible: false, + header: 'Also Known As', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Previously Known As') }, { field: 'Description', - title: 'Description', - sortable: true, - visible: true, + header: 'Description', + isSortable: true, + showColumn: true, class: 'wid-25', formatter: this.sharedService.formatDescription, titleTooltip: this.getTooltip('Description') }, { field: 'Category', - title: 'Category', - sortable: true, + header: 'Category', + isSortable: true, titleTooltip: this.getTooltip('Category') }, { field: 'Status', - title: 'Status', - sortable: true, + header: 'Status', + isSortable: true, titleTooltip: this.getTooltip('Status') }, { field: 'StandardType', - title: 'Standard Type', - sortable: true, - visible: false, + header: 'Standard Type', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Standard Type') }, { field: 'DeploymentType', - title: 'Deployment Type', - sortable: true, + header: 'Deployment Type', + isSortable: true, titleTooltip: this.getTooltip('Deployment Type') }, { field: 'ComplianceStatus', - title: '508 Compliance', - sortable: true, - visible: false, + header: '508 Compliance', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('508 Compliance') }, { field: 'POC', - title: 'POC', - sortable: true, - visible: false, + header: 'POC', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('POC') }, { field: 'POCorg', - title: 'POC Org', - sortable: true, - visible: false, + header: 'POC Org', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('POC Org') }, { field: 'Comments', - title: 'Comments', - sortable: true, - visible: false, + header: 'Comments', + isSortable: true, + showColumn: false, formatter: this.sharedService.formatDescription, titleTooltip: this.getTooltip('Comments') }, { field: 'attestation_required', - title: 'Attestation Required', - sortable: true, - visible: false, + header: 'Attestation Required', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Attestation Required') }, { field: 'attestation_link', - title: 'Attestation Link', - sortable: true, - visible: false, + header: 'Attestation Link', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Attestation Link') }, { field: 'fedramp', - title: 'FedRAMP', - sortable: true, - visible: false, + header: 'FedRAMP', + isSortable: true, + showColumn: false, formatter: this.YesNo, titleTooltip: this.getTooltip('FedRAMP') }, { field: 'open_source', - title: 'Open Source', - sortable: true, - visible: false, + header: 'Open Source', + isSortable: true, + showColumn: false, formatter: this.YesNo, titleTooltip: this.getTooltip('Open Source') },{ field: 'RITM', - title: 'Requested Item (RITM)', - sortable: true, - visible: false, + header: 'Requested Item (RITM)', + isSortable: true, + showColumn: false, titleTooltip: this.getTooltip('Requested Item (RITM)') }, { field: 'ApprovalExpirationDate', - title: 'Approval Expires', - sortable: true, - visible: true, + header: 'Approval Expires', + isSortable: true, + showColumn: true, formatter: this.sharedService.dateFormatter, titleTooltip: this.getTooltip('Approval Expiration Date') }, { field: 'ApprovedVersions', - title: 'Approved Versions', - sortable: false, - visible: true, + header: 'Approved Versions', + isSortable: false, + showColumn: true, titleTooltip: this.getTooltip('Approved Versions') }]; - $('#itStandardsTable').bootstrapTable( - $.extend(this.tableOptions, { - columns: this.columnDefs, - data: [], - }) - ); + this.dataReady = true; }); // Enable popovers @@ -260,20 +255,7 @@ export class ItStandardsComponent implements OnInit { // Set JWT when logged into GEAR Manager when returning from secureAuth this.sharedService.setJWTonLogIn(); - const self = this; - $(document).ready(() => { - // Method to handle click events on the Investments table - $('#itStandardsTable').on('click-row.bs.table', function (e, row) { - this.tableService.itStandTableClick(row); - }.bind(this) - ); - - //Method to Enable table sticky header after table commponent initialized. - $('#itStandardsTable').on('load-success.bs.table', function () { - this.sharedService.enableStickyHeader("itStandardsTable"); - }.bind(this) - ); - }); + this.apiService.getITStandards().subscribe(i => this.tableData = i); // Method to open details modal when referenced directly via URL this.route.params.subscribe((params) => { @@ -308,43 +290,6 @@ export class ItStandardsComponent implements OnInit { $('#divRelease').addClass("disabledDivRelease"); } - // Update table from filter buttons - changeFilter(field: string, term: string) { - this.sharedService.disableStickyHeader("itStandardsTable"); - this.filteredTable = true; // Filters are on, expose main table button - var filter = {}; - filter[field] = term; - - // Set cloud field to visible if filtering by cloud enabled - $('#itStandardsTable').bootstrapTable('filterBy', filter); - $('#itStandardsTable').bootstrapTable('refreshOptions', { - exportOptions: { - fileName: this.sharedService.fileNameFmt( - 'GSA_' + term + '_IT_Standards' - ), - }, - }); - - this.filterTitle = `${term} `; - this.sharedService.enableStickyHeader("itStandardsTable"); - } - - backToMainIT() { - this.sharedService.disableStickyHeader("itStandardsTable"); - this.filteredTable = false; // Hide main button - - // Remove filters and back to default - $('#itStandardsTable').bootstrapTable('filterBy', {}); - $('#itStandardsTable').bootstrapTable('refreshOptions', { - exportOptions: { - fileName: this.sharedService.fileNameFmt('GSA_IT_Standards'), - }, - }); - - this.filterTitle = ''; - this.sharedService.enableStickyHeader("itStandardsTable"); - } - getTooltip (name: string): string { const def = this.attrDefinitions.find(def => def.Term === name); if(def){ diff --git a/tsconfig.json b/tsconfig.json index 493a2add..f2dc9c35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "experimentalDecorators": true, "module": "esnext", "moduleResolution": "node", + "resolveJsonModule": true, "importHelpers": true, "target": "ES2022", "lib": [ From 9021a52810869e4644d937a14cffccedc0ade440 Mon Sep 17 00:00:00 2001 From: Jonah Hatfield Date: Thu, 24 Oct 2024 11:38:49 -0400 Subject: [PATCH 18/20] Making the rows of the websites service category modal table clickable, satisfying one item of issue #508 --- angular.json | 12 ++++++------ .../website-service-category-modal.component.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/angular.json b/angular.json index c9fe1fba..f2034d88 100644 --- a/angular.json +++ b/angular.json @@ -86,8 +86,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "4mb", - "maximumError": "5mb" + "maximumWarning": "6mb", + "maximumError": "7mb" }, { "type": "anyComponentStyle", @@ -113,8 +113,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "4mb", - "maximumError": "5mb" + "maximumWarning": "6mb", + "maximumError": "7mb" }, { "type": "anyComponentStyle", @@ -134,8 +134,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "4mb", - "maximumError": "5mb" + "maximumWarning": "6mb", + "maximumError": "7mb" }, { "type": "anyComponentStyle", diff --git a/src/app/components/modals/website-service-category-modal/website-service-category-modal.component.ts b/src/app/components/modals/website-service-category-modal/website-service-category-modal.component.ts index cee16438..547aaa43 100644 --- a/src/app/components/modals/website-service-category-modal/website-service-category-modal.component.ts +++ b/src/app/components/modals/website-service-category-modal/website-service-category-modal.component.ts @@ -96,10 +96,10 @@ export class WebsiteServiceCategoryModalComponent implements OnInit { const self = this; $(document).ready(() => { // Method to handle click events on the Related Systems table - $('#serviceCategoryRelSysTable').on('click-row.bs.table', function (e, row) { + $('#websiteServiceCategoryWebsites').on('click-row.bs.table', function (e, row) { // Hide First Modal before showing new modal $('#websiteServiceCategoryDetail').modal('hide'); - self.tableService.systemsTableClick(row); + self.tableService.websitesTableClick(row); }.bind(this)); }); From 8f29e1a4cf9471b02e4f14d95cd9721aacf4fc89 Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:20:25 -0700 Subject: [PATCH 19/20] comning all commits for #456 & #507 tasks to remove data --- api/controllers/base.controller.js | 2111 +++++++++-------- api/controllers/cron.controller.js | 94 +- api/controllers/records.controller.js | 34 +- .../touchpoint-import.controller.js | 175 -- api/cron-jobs/cron-job-db-util.service.js | 49 + api/cron-jobs/job-logger.js | 34 + api/cron-jobs/poc-job-handler.js | 131 + api/cron-jobs/tpi-job-handler.js | 189 ++ api/db.js | 5 +- api/enums/job-status.js | 7 + api/queries/CREATE/insert_cron_job.sql | 1 + .../GET/get_any_pending_job_by_type.sql | 9 + api/queries/UPDATE/update_cron_job_status.sql | 5 + api/util/csv-parse-service.js | 28 + api/util/date-time-format-service.js | 36 + api/util/db-query-service.js | 53 + api/util/https-client-service.js | 44 + api/util/io-service.js | 24 + api/util/json-transform-engine.js | 55 +- server.js | 84 +- 20 files changed, 1796 insertions(+), 1372 deletions(-) delete mode 100644 api/controllers/touchpoint-import.controller.js create mode 100644 api/cron-jobs/cron-job-db-util.service.js create mode 100644 api/cron-jobs/job-logger.js create mode 100644 api/cron-jobs/poc-job-handler.js create mode 100644 api/cron-jobs/tpi-job-handler.js create mode 100644 api/enums/job-status.js create mode 100644 api/queries/CREATE/insert_cron_job.sql create mode 100644 api/queries/GET/get_any_pending_job_by_type.sql create mode 100644 api/queries/UPDATE/update_cron_job_status.sql create mode 100644 api/util/csv-parse-service.js create mode 100644 api/util/date-time-format-service.js create mode 100644 api/util/db-query-service.js create mode 100644 api/util/https-client-service.js create mode 100644 api/util/io-service.js diff --git a/api/controllers/base.controller.js b/api/controllers/base.controller.js index 0b1e2de3..f8df87df 100644 --- a/api/controllers/base.controller.js +++ b/api/controllers/base.controller.js @@ -1,6 +1,6 @@ const sql = require("../db.js").connection, // sql_cowboy = require("../db.js").connection_cowboy, - sql_promise = require("../db.js").connection_promise, + connPromisePool = require("../db.js").promisePool, path = require("path"), fs = require("fs"), readline = require("readline"), @@ -13,6 +13,8 @@ const SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"]; // created automatically when the authorization flow completes for the first // time. const TOKEN_PATH = "token.json"; +const JobStatus = require('../enums/job-status.js'); +const { formatDateTime } = require('../util/date-time-format-service.js'); exports.getApiToken = async (req, res) => { //console.log('req.headers: ', req.headers); //debugging @@ -20,7 +22,7 @@ exports.getApiToken = async (req, res) => { //let [rows, fields] = await sql_promise.query(`CALL gear_acl.verifyJwt ('${req.headers.requester}', '${req.headers.apitoken}');`); - let [rows, fields] = await sql_promise.query(`select count(*) as sessions_cnt from gear_acl.logins where email = '${req.headers.requester}' and jwt = '${req.headers.apitoken}';`); + let [rows, fields] = await connPromisePool.query(`select count(*) as sessions_cnt from gear_acl.logins where email = '${req.headers.requester}' and jwt = '${req.headers.apitoken}';`); return rows[0].sessions_cnt; @@ -29,7 +31,7 @@ exports.getApiToken = async (req, res) => { //return response; } -exports.sendQuery = (query, msg, response, postProcessFunc=null) => { +exports.sendQuery = (query, msg, response, postProcessFunc = null) => { return buildQuery(sql, query, msg, response, postProcessFunc); }; @@ -49,7 +51,7 @@ exports.sendLogQuery = (event, user, msg, response) => { * @param {string} msg - A description of the query that will be printed if an error occurs * @return {object} - query results as JSON */ -function buildQuery(conn, query, msg, response, postProcessFunc=null) { +function buildQuery(conn, query, msg, response, postProcessFunc = null) { conn.query(query, (error, data) => { if (error) { console.log(`DB Query Error while executing ${msg}: `, error); @@ -80,12 +82,12 @@ function buildLogQuery(conn, event, user, msg, response) { // var query = `insert into gear_log.event (Event, User, DTG) values ('${event}', '${user}', now());`; console.log(query); - + // conn.query(query, (error, data) => { if (error) { console.log(`DB Log Event Query Error while executing ${msg}: `, error); - return {message: error.message || `DB Query Error while executing ${msg}`,}; + return { message: error.message || `DB Query Error while executing ${msg}`, }; } else { //console.log("Event Logged"); // Debug return JSON.stringify(data); @@ -93,6 +95,20 @@ function buildLogQuery(conn, event, user, msg, response) { }); } +async function buildLogQueryAsync(conn, event, user, msg) { + // + var query = `insert into gear_log.event (Event, User, DTG) values ('${event}', '${user}', now());`; + console.log(query); + try { + await conn.promise().query(query); + return JSON.stringify(data); + } + catch (error) { + console.log(`DB Log Event Query Error while executing ${msg}: `, error); + return { message: error.message || `DB Query Error while executing ${msg}`, }; + } +} + exports.emptyTextFieldHandler = (content) => { if (!content) return "NULL"; else return `'${content}'`; @@ -103,76 +119,114 @@ exports.setEmptyTextFieldHandler = (content) => { else return content; }; +const msgLog = (message, logger) => { + if (logger) { + logger.log(message); + } + console.log(message); +} + +async function readFileAsync(path, disableLogQuery, response, requester, jobLogger, jobId) { + try { + return await fs.promises.readFile(path, 'utf8'); + } catch (err) { + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - ERROR: loading client secret file`, requester, "log_update_zk_systems_subsystems_records", response); + } + if (requester === "GearCronJ") { + msgLog(`Error loading client secret file: ${err} `, jobLogger); + msgLog(err.stack, jobLogger); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } else { + response.status(504).json({ error: "Error loading client secret file: " + err }); + } + } +} + +async function readFileAsync2(path, disableLogQuery, response, requester, jobLogger, jobId) { + try { + return await fs.promises.readFile(path, 'utf8'); + } catch (err) { + let errMessage = "Reading the Token returned an error: " + err; + errMessage = errMessage.replace(/'/g, ""); + msgLog(errMessage, jobLogger); + if (requester === "GearCronJ") { + msgLog(err.stack, jobLogger); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } else { + sendResponse(response, { error: errMessage }); + } + } +} /* **** Google API **** All this needs to be refactored as to not be so redundant*/ -exports.googleMain = (response, method, sheetID, dataRange, requester, key = null) => { - console.log("googleMain()"); +exports.googleMain = async (response, method, sheetID, dataRange, requester, key = null, jobLogger = null, jobId = null, postprocesJobExecution = null) => { + + msgLog("googleMain()", jobLogger); + + const disableLogQuery = jobLogger ? true : false; // get the current date and format it as yyyymmddhh let date = new Date(); let formattedDate = null; if (requester === "GearCronJ") { - formattedDate = `${date.getFullYear()}${String((date.getMonth()+1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}`; + formattedDate = `${date.getFullYear()}${String((date.getMonth() + 1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}`; } else { - formattedDate = `${date.getFullYear()}${String((date.getMonth()+1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}${String(date.getMinutes()).padStart(2, "0")}`; + formattedDate = `${date.getFullYear()}${String((date.getMonth() + 1)).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}${String(date.getHours()).padStart(2, "0")}${String(date.getMinutes()).padStart(2, "0")}`; } - /*sql.query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`, (error, data) => { + try { + // log the start of the refresh to the database + if (!disableLogQuery) { + await sql.promise().query(`insert into gear_schema.google_api_run_log (id) values ('${formattedDate}');`); + await buildLogQueryAsync(sql, `Update All Related Records - Starting`, requester, "log_update_zk_systems_subsystems_records", response); + } - if (error) { - console.log(`Duplicate Google Sheets API Request: `, error); + // Load client secrets from a local file. + const content = await readFileAsync("certs/gear_google_credentials.json", disableLogQuery, response, requester, jobLogger, jobId); + if (!content) { + return; + } - if (requester === "GearCronJ") { - console.log("Duplicate Google Sheets API Request: " + error); - return; - } else { - response = response.status(504).json({ error: "Duplicate Google Sheets API Request: " + error }); - return; - } + // Set callback based on method + const callback_method = method === "all" ? retrieveAll + : method === "single" ? single + : method === "refresh" ? refresh : null; + msgLog(`callback_method: ${method}`, jobLogger); + + // Authorize a client with credentials, then call the Google Sheets API. + await authorize(JSON.parse(content), callback_method, response, sheetID, + dataRange, requester, key, jobLogger, jobId, postprocesJobExecution, disableLogQuery); + } catch (error) { + msgLog(`Duplicate Google Sheets API Request: ${error}`, jobLogger); + if (requester === "GearCronJ") { + msgLog(error.stack, jobLogger); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); } else { - // log the start of the refresh to the database - buildLogQuery(sql, `Update All Related Records - Starting`, requester, "log_update_zk_systems_subsystems_records", response); -*/ - // Load client secrets from a local file. - fs.readFile("certs/gear_google_credentials.json", (err, content) => { - if (err) { - buildLogQuery(sql, `Update All Related Records - ERROR: loading client secret file`, requester, "log_update_zk_systems_subsystems_records", response); + response.status(504).json({ error: "Duplicate Google Sheets API Request: " + error }); + } + } +}; - if (requester === "GearCronJ") { - console.log("Error loading client secret file: " + err); - return; - } else { - response = response.status(504).json({ error: "Error loading client secret file: " + err }); - return; - } - } +async function getOAuth2Client(credentials) { + var client_secret = credentials.client_secret; + var client_id = credentials.client_id; + var redirect_uris = credentials.redirect_uris; - // Set callback based on method - var callback_method = null; - - if (method === "all") callback_method = retrieveAll; - else if (method === "single") callback_method = single; - else if (method === "refresh") callback_method = refresh; - - console.log("callback_method: ", callback_method); - - // Authorize a client with credentials, then call the Google Sheets API. - authorize( - JSON.parse(content), - callback_method, - response, - sheetID, - dataRange, - requester, - key - ); + const oAuth2Client = new google.auth.OAuth2( + client_id, client_secret, redirect_uris + ); + oAuth2Client.on("tokens", async (tokens) => { + if (tokens.refresh_token) { + oAuth2Client.setCredentials({ + refresh_token: tokens.refresh_token, }); - - /*} - });*/ -}; + } + }); + return oAuth2Client; +} /** * Create an OAuth2 client with the given credentials, and then execute the @@ -181,51 +235,45 @@ exports.googleMain = (response, method, sheetID, dataRange, requester, key = nul * @param {function} callback The callback to call with the authorized client. * @param {response} response Response Object */ -function authorize( +async function authorize( credentials, callback, response, sheetID, dataRange, requester, - key = null + key = null, + jobLogger = null, + jobId = null, + postprocesJobExecution = null, + disableLogQuery = false, ) { - var client_secret = credentials.client_secret; - var client_id = credentials.client_id; - var redirect_uris = credentials.redirect_uris; - - const oAuth2Client = new google.auth.OAuth2( - client_id, client_secret, redirect_uris -); - - try { - // Check if we have previously stored a token. - fs.readFile(TOKEN_PATH, (err, token) => { - if (err) { - let errMessage = "Reading the Token returned an error: " + err; - errMessage = errMessage.replace(/'/g, ""); - - sendResponse(response, { error: errMessage }); - return; - } - - oAuth2Client.setCredentials(JSON.parse(token)); - oAuth2Client.on("tokens", (tokens) => { - if (tokens.refresh_token) { - oAuth2Client.setCredentials({ - refresh_token: tokens.refresh_token, - }); - } - }); - - if (!key) callback(oAuth2Client, response, sheetID, dataRange, requester); - else callback(oAuth2Client, response, sheetID, dataRange, requester, key); - }); - } catch (err) { - // log the error to the database - buildLogQuery(sql, `Update All Related Records - Token Issue: ` || json.stringify(err), requester, `log_update_zk_systems_subsystems_records`, response); - sendResponse(response, { error: "Reading the Token returned an error: " + err }); + const oAuth2Client = await getOAuth2Client(credentials); + try { + // Check if we have previously stored a token. + const token = await readFileAsync2(TOKEN_PATH, disableLogQuery, response, requester, jobLogger, jobId); + if (!token) { + return; } + oAuth2Client.setCredentials(JSON.parse(token)); + + return !key ? await callback(oAuth2Client, response, sheetID, dataRange, requester, + jobLogger, jobId, postprocesJobExecution, disableLogQuery) + : await callback(oAuth2Client, response, sheetID, dataRange, requester, + jobLogger, jobId, postprocesJobExecution, disableLogQuery, key); + } catch (err) { + // log the error to the database + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - Token Issue: ` || json.stringify(err), requester, `log_update_zk_systems_subsystems_records`, response); + } + const errMessage = "Reading the Token returned an error: " + err; + if (requester === "GearCronJ") { + msgLog(errMessage, jobLogger); + msgLog(err.stack, jobLogger); + } else { + sendResponse(response, { error: errMessage }); + } + } } /** @@ -233,178 +281,193 @@ function authorize( * @param {google.auth.OAuth2} auth The authenticated Google OAuth client. * @param response Response object */ -function retrieveAll(auth, response, sheetID, dataRange, requester) { - const sheets = google.sheets({ version: "v4", auth }); +async function retrieveAll(auth, response, sheetID, dataRange, requester, jobLogger, jobId, postprocesJobExecution, disableLogQuery) { - sheets.spreadsheets.values.get( - { + try { + const sheets = google.sheets({ version: "v4", auth }); + const res = await sheets.spreadsheets.values.get({ spreadsheetId: sheetID, range: dataRange, - }, - (err, res) => { - if (err) { - sendResponse(response, { error: "The API returned an error: " + err }); - return; - } - - const rows = res.data.values; - if (rows.length) { - const headers = rows[0]; - var data = []; - - // Structure rows into an object - for (i = 1; i < rows.length; i++) { - row = {}; - for (j = 0; j < headers.length; j++) { - row[headers[j]] = rows[i][j]; - } - data.push(row); + }); + const rows = res.data.values; + if (rows.length) { + const headers = rows[0]; + var data = []; + + // Structure rows into an object + for (i = 1; i < rows.length; i++) { + row = {}; + for (j = 0; j < headers.length; j++) { + row[headers[j]] = rows[i][j]; } - sendResponse(response, data); + data.push(row); } + sendResponse(response, data); } - ); + } catch (error) { + if (requester === "GearCronJ") { + msgLog("The API returned an error: " + err, jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } + } else { + sendResponse(response, { error: "The API returned an error: " + err }); + } + } } // This function refreshes the data in the database using the data from the spreadsheet -function refresh(auth, response, sheetID, dataRange, requester) { +async function refresh(auth, response, sheetID, dataRange, requester, jobLogger, jobId, postprocesJobExecution, disableLogQuery) { + try { + // log the start of the refresh to the database + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - Refreshing Data`, requester, "log_update_zk_systems_subsystems_records", response); + } + msgLog("Update All Related Records - Refreshing Data: log_update_zk_systems_subsystems_records", jobLogger) - // log the start of the refresh to the database - buildLogQuery(sql, `Update All Related Records - Refreshing Data`, requester, "log_update_zk_systems_subsystems_records", response); - - // Get the data from the spreadsheet - const sheets = google.sheets({ version: "v4", auth }); - sheets.spreadsheets.values.get( - { + // Get the data from the spreadsheet + const sheets = google.sheets({ version: "v4", auth }); + const res = await sheets.spreadsheets.values.get({ spreadsheetId: sheetID, range: dataRange, - }, - (err, res) => { - - // If there is an error with the API call to the spreadsheet return the error - if (err) { - console.log("Google api error::: Start") - console.log(sheetID) - console.log(dataRange) - console.log(err) - console.log("Google api error::: End") - buildLogQuery(sql, `Update All Related Records - ERROR: Google Sheets API returned...\n${err.message}`, requester, "log_update_zk_systems_subsystems_records", response); - - if (requester === "GearCronJ") { - console.log("The API returned an error: " + err); - return; - } else { - sendResponse(response, { error : "The API returned an error: " + err }); - return; + }); + + // Get the rows from the spreadsheet + const rows = res.data.values; + // If rows is not empty + if (rows.length <= 0 || rows == undefined) { + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - ERROR: No Data Found`, requester, "log_update_zk_systems_subsystems_records", response); + } + if (requester === "GearCronJ") { + msgLog("No data found.", jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.SUCCESS); } + return; + } else { + sendResponse(response, { error: "No data found." }); + return; } + } - // Get the rows from the spreadsheet - const rows = res.data.values; - - // If rows is not empty - if (rows.length <= 0 || rows == undefined) { - buildLogQuery(sql, `Update All Related Records - ERROR: No Data Found`, requester, "log_update_zk_systems_subsystems_records", response); - - if (requester === "GearCronJ") { - console.log("No data found."); - return; - } else { - sendResponse(response, { error: "No data found." }); - return; - } + msgLog("Mapping values...", jobLogger); + // Map values + var m = new Map(); + // Keep track of how many rows are being processed + rowCounter = 0 + // Map records to systems + rows.forEach((r) => { + // If the map does not contain the key, create an empty array + if (!m.has(r[1])) { + //Insert new key with empty array + m.set(r[1], []); } - console.log("Mapping values...") - // Map values - var m = new Map(); - - // Keep track of how many rows are being processed - rowCounter = 0 - - // Map records to systems - rows.forEach((r) => { - // If the map does not contain the key, create an empty array - if (!m.has(r[1])) { - //Insert new key with empty array - m.set(r[1], []); - } - // Push the value into the array + // Push the value into the array + if (m.get(r[1]).indexOf(r[0]) === -1) { m.get(r[1]).push(r[0]); + // increment the rowCounter + rowCounter++ + } + }) - // increment the rowCounter - rowCounter++ - }) - - // Build DML statements from the map ================================================================================ - console.log("Building DML Statements...") + // Build DML statements from the map ================================================================================ + msgLog("Building DML Statements...", jobLogger) - // Insert new IDs - let systemString = "" + // Insert new IDs + let systemString = "" - // Keep track of how many DML statements are being sent - dmlStatementCounter = 0 + // Keep track of how many DML statements are being sent + dmlStatementCounter = 0 - // Keep track of how many inserts are being sent - insertCounter = 0 + // Keep track of how many inserts are being sent + insertCounter = 0 - // Iterate through the map - for (let recordsId of m.keys()) { - // Delete all records for the given recordsId - systemString += `DELETE FROM zk_systems_subsystems_records WHERE obj_records_Id=${recordsId}; `; + // Iterate through the map + for (let recordsId of m.keys()) { + // Delete all records for the given recordsId + systemString += `DELETE FROM zk_systems_subsystems_records WHERE obj_records_Id=${recordsId}; `; + dmlStatementCounter++ + // Insert new records for the given recordsId + for (let systemId of m.get(recordsId)) { + // Append the DML statement to the string + systemString += `INSERT INTO zk_systems_subsystems_records (obj_records_Id, obj_systems_subsystems_Id) VALUES (${recordsId}, ${systemId}); `; dmlStatementCounter++ - // Insert new records for the given recordsId - for (let systemId of m.get(recordsId)) { - // Append the DML statement to the string - systemString += `INSERT INTO zk_systems_subsystems_records (obj_records_Id, obj_systems_subsystems_Id) VALUES (${recordsId}, ${systemId}); `; - dmlStatementCounter++ - insertCounter++ - } + insertCounter++ } + } - console.log("Sending DML Statements: " + dmlStatementCounter) - - // Send the DML statements to the database - sql.query(`${systemString}`, (error, data) => { - let date = new Date(); - let msg = "Sending refresh all query using Google Sheet" - - // Send the response - if (error) { - console.log(`DB Query Error while executing ${msg}: `, error); + msgLog("Sending DML Statements: " + dmlStatementCounter, jobLogger) - // log the error to the database - buildLogQuery(sql, `Update All Related Records - ERROR: ${msg}: ` || error.message, requester, `log_update_zk_systems_subsystems_records`, response); + // Send the DML statements to the database + await executeDmlStmts(sql, systemString, rowCounter, insertCounter, response, requester, jobLogger, jobId, postprocesJobExecution, disableLogQuery); + } catch (error) { + msgLog("Google api error::: Start", jobLogger); + msgLog(sheetID, jobLogger); + msgLog(dataRange, jobLogger); + msgLog(error, jobLogger); + msgLog(error.stack, jobLogger); + msgLog("Google api error::: End", jobLogger); + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - ERROR: Google Sheets API returned...\n${error.message}`, requester, "log_update_zk_systems_subsystems_records", response); + } + if (requester === "GearCronJ") { + msgLog("The API returned an error: " + error, jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } + } else { + sendResponse(response, { error: "The API returned an error: " + error }); + } + } +} - if (requester === "GearCronJ") { - console.log(error.message || `DB Query Error while executing ${msg}`); - return; - } else { - response.status(501).json({message: error.message || `DB Query Error while executing ${msg}`,}); - } - } else { - // log the success to the database - buildLogQuery(sql, `Update All Related Records - ${insertCounter} rows inserted successfully`, requester, `log_update_zk_systems_subsystems_records`, response); +async function executeDmlStmts(sql, systemString, rowCounter, insertCounter, response, requester, jobLogger, jobId, postprocesJobExecution, disableLogQuery) { + let msg = "Sending refresh all query using Google Sheet"; + try { + const data = await sql.promise().query(`${systemString}`); - const summary = { - "tot_executions": dmlStatementCounter, - "tot_inserts": insertCounter, - "tot_rows": rowCounter, - "ran_by": requester, - "last_ran": (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(), - }; + let date = new Date(); + // log the success to the database + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - ${insertCounter} rows inserted successfully`, + requester, `log_update_zk_systems_subsystems_records`, response); + } + const summary = { + "tot_executions": dmlStatementCounter, + "tot_inserts": insertCounter, + "tot_rows": rowCounter, + "ran_by": requester, + "last_ran": (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(), + }; - if (requester === "GearCronJ") { - console.log(summary); - return; - } else { - response.status(200).json(summary); - } - } - console.log("Finished sending DML Statements") - }); + if (requester === "GearCronJ") { + msgLog(JSON.stringify(summary), jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.SUCCESS); + } + } else { + response.status(200).json(summary); } - ); + console.log("Finished sending DML Statements"); + } catch (error) { + msgLog(`DB Query Error while executing ${msg}: `, jobLogger); + msgLog(error, jobLogger); + // log the error to the database + if (!disableLogQuery) { + await buildLogQueryAsync(sql, `Update All Related Records - ERROR: ${msg}: ` || error.message, requester, `log_update_zk_systems_subsystems_records`, response); + } + if (requester === "GearCronJ") { + msgLog(error.message || `DB Query Error while executing ${msg}`, jobLogger); + msgLog(error.stack, jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } + } else { + response.status(501).json({ message: error.message || `DB Query Error while executing ${msg}`, }); + } + } } /** @@ -413,55 +476,56 @@ function refresh(auth, response, sheetID, dataRange, requester) { * @param {google.auth.OAuth2} auth The authenticated Google OAuth client. * @param response Response object */ -function single(auth, response, sheetID, dataRange, requester, key) { - const sheets = google.sheets({ version: "v4", auth }); +async function single(auth, response, sheetID, dataRange, requester, jobLogger, jobId, postprocesJobExecution, disableLogQuery, key) { - // Grab all data first - sheets.spreadsheets.values.get( - { + try { + const sheets = google.sheets({ version: "v4", auth }); + const res = await sheets.spreadsheets.values.get({ spreadsheetId: sheetID, range: dataRange, - }, - (err, res) => { - if (err) { - sendResponse(response, { error: "The API returned an error: " + err }); - return; - } + }); - const rows = res.data.values; - if (rows.length) { - const headers = rows[0]; - var data = []; + const rows = res.data.values; + if (rows.length) { + const headers = rows[0]; + var data = []; - // Structure rows into an object - for (i = 1; i < rows.length; i++) { - row = {}; - for (j = 0; j < headers.length; j++) { - row[headers[j]] = rows[i][j]; - } - data.push(row); - } - - // Send error if no data - if (!data) { - sendResponse(response, null); - return; + // Structure rows into an object + for (let i = 1; i < rows.length; i++) { + row = {}; + for (j = 0; j < headers.length; j++) { + row[headers[j]] = rows[i][j]; } + data.push(row); + } - // Filter down to desired ID - var singleID = data.filter(function (d) { - if (d.Id) { - return d.Id === key; - } else if (d.Rec_ID) { - return d.Rec_ID === key; - } - }); + // Send error if no data + if (!data) { + sendResponse(response, null); + return; + } - sendResponse(response, singleID); - return singleID; + // Filter down to desired ID + var singleID = data.filter(function (d) { + if (d.Id) { + return d.Id === key; + } else if (d.Rec_ID) { + return d.Rec_ID === key; + } + }); + sendResponse(response, singleID); + return singleID; + } + } catch (error) { + if (requester === "GearCronJ") { + msgLog("The API returned an error: " + err, jobLogger); + if (postprocesJobExecution) { + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); } + } else { + sendResponse(response, { error: "The API returned an error: " + err }); } - ); + } } // Send the response @@ -488,7 +552,7 @@ exports.updatePocs = (req, res) => { pocCsv.shift(); let query = "REPLACE INTO obj_ldap_poc (SamAccountName, FirstName, LastName, Email, Phone, OrgCode, Position, EmployeeType) VALUES ?"; - + sql.query(query, [pocCsv], (error, response) => { console.log(error || response); }); @@ -514,20 +578,20 @@ const logFolderPath = `tech_catalog_data/logs`; // path for main log folder // import component functions function getValidDatasets() { - const validDatasets = - ['Manufacturer', - 'Platform', - 'SoftwareEdition', - 'SoftwareFamily', - 'SoftwareLifecycle', - 'SoftwareMarketVersion', - 'SoftwareProduct', - 'SoftwareProductLink', - 'SoftwareRelease', - 'SoftwareReleaseLink', - 'SoftwareReleasePlatform', - 'SoftwareVersion', - 'Taxonomy']; + const validDatasets = + ['Manufacturer', + 'Platform', + 'SoftwareEdition', + 'SoftwareFamily', + 'SoftwareLifecycle', + 'SoftwareMarketVersion', + 'SoftwareProduct', + 'SoftwareProductLink', + 'SoftwareRelease', + 'SoftwareReleaseLink', + 'SoftwareReleasePlatform', + 'SoftwareVersion', + 'Taxonomy']; return validDatasets; } @@ -582,7 +646,7 @@ async function validateRequest(data, logHeader = null) { // TO DO: validating importId - + if (result) { logger(`${formatDateTime(new Date())}${logHeader}`, `... request parameters are valid`); @@ -598,14 +662,14 @@ async function getLastRecordId(tableName, logHeader = null) { // - parameters: tableName (string) // - returns: lastRecordId (string) - let timer = timer (); + let timer = timer(); logger(`${formatDateTime(new Date())}${logHeader}`, `... getting the last id from ${tableName}`); let lastRecordId = null; // get the max id from the database - let [rows, fields] = await sql_promise.query(`select max(id) as lastId from ${tableName};`); + let [rows, fields] = await connPromisePool.query(`select max(id) as lastId from ${tableName};`); // set the lastRecordId lastRecordId = rows[0].lastId; @@ -632,7 +696,7 @@ async function getLastSyncDate(tableName, logHeader = null) { let lastSynchronizedDate = null; // send request for the max synchronizedDate from the database - let [rows, fields] = await sql_promise.query(`select max(synchronizedDate) as lastSynchronizedDate from ${tableName};`); + let [rows, fields] = await connPromisePool.query(`select max(synchronizedDate) as lastSynchronizedDate from ${tableName};`); // get lastSynchronizedDate from response lastSynchronizedDate = rows[0].lastSynchronizedDate; @@ -659,7 +723,7 @@ async function getTableRecordCount(tableName, logHeader = null) { let count = null; // send request - let [rows, fields] = await sql_promise.query(`select count(*) as totalCount from ${tableName};`); + let [rows, fields] = await connPromisePool.query(`select count(*) as totalCount from ${tableName};`); // get lastSynchronizedDate from response count = rows[0].totalCount; @@ -694,7 +758,7 @@ async function getAccessToken(refreshToken, logHeader = null) { grant_type: 'refresh_token', refresh_token: refreshToken, }), -}); + }); // check if response is ok if (response.ok) { @@ -709,7 +773,7 @@ async function getAccessToken(refreshToken, logHeader = null) { if (accessToken === null || accessToken === '') { throw `access token api call returned null`; } - + } else { throw `access token api call returned bad response: ${response.status} ${response.statusText}`; } @@ -732,7 +796,7 @@ async function buildAPIQuery(datasetName, takeAmt, afterId, queryColumnType, isT let graphqlQuery = null; // stores the graphql query to return let additionalQueryParameters = ''; // stores the additional graphql query parameters let columnList = ''; // stores the column list to add to graphql query - + // ... check if requesting for specific record if (recordId !== null) { // ... add recordId to the query @@ -748,9 +812,9 @@ async function buildAPIQuery(datasetName, takeAmt, afterId, queryColumnType, isT if (isToBeDeleted !== null) { additionalQueryParameters = additionalQueryParameters + ` isToBeDeleted: ${isToBeDeleted}`; } - + if (queryColumnType === 'all') { - + // ... select column list to add to graphql query to get data for ALL columns switch (await datasetName) { case 'Manufacturer': @@ -1040,11 +1104,11 @@ async function buildAPIQuery(datasetName, takeAmt, afterId, queryColumnType, isT toBeDeletedOn updatedDate `; - + } else { throw `invalid queryColumnType provided when building graphql query`; } - + // build graphql query graphqlQuery = JSON.stringify({ query: `{ @@ -1063,7 +1127,7 @@ async function buildAPIQuery(datasetName, takeAmt, afterId, queryColumnType, isT } async function sendAPIQuery(graphqlQuery, accessToken, logHeader = null) { - + // - description: sends the graphql query to flexeras api and returns the json response // - parameters: graphqlQuery (string), accessToken (string) // - returns: pageJson (json object) @@ -1111,265 +1175,265 @@ function getInsertColumnsList(datasetName) { switch (datasetName) { case 'Manufacturer': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'acquiredDate,' // dt:DATETIME - + 'city,' // dt:VARCHAR - + 'country,' // dt:VARCHAR - + 'createdDate,' // dt:DATETIME - + 'deleteReason,' // dt:VARCHAR - + 'description,' // dt:VARCHAR - + 'email,' // dt:VARCHAR - + 'employees,' // dt:VARCHAR - + 'employeesDate,' // dt:DATETIME - + 'fax,' // dt:VARCHAR - + 'fiscalEndDate,' // dt:DATETIME - + 'isPubliclyTraded,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'knownAs,' // dt:VARCHAR - + 'legal,' // dt:VARCHAR - + 'name,' // dt:VARCHAR - + 'ownerId,' // dt:VARCHAR - + 'phone,' // dt:VARCHAR - + 'profitsDate,' // dt:DATETIME - + 'profitsPerYear,' // dt:INT - + 'replacementId,' // dt:VARCHAR - + 'revenue,' // dt:INT - + 'revenueDate,' // dt:DATETIME - + 'state,' // dt:VARCHAR - + 'street,' // dt:VARCHAR - + 'symbol,' // dt:VARCHAR - + 'synchronizedDate,' // dt:DATETIME - + 'tier,' // dt:INT - + 'toBeDeletedOn,' // dt:DATETIME - + 'updatedDate,' // dt:DATETIME - + 'website,' // dt:VARCHAR - + 'zip,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'acquiredDate,' // dt:DATETIME + + 'city,' // dt:VARCHAR + + 'country,' // dt:VARCHAR + + 'createdDate,' // dt:DATETIME + + 'deleteReason,' // dt:VARCHAR + + 'description,' // dt:VARCHAR + + 'email,' // dt:VARCHAR + + 'employees,' // dt:VARCHAR + + 'employeesDate,' // dt:DATETIME + + 'fax,' // dt:VARCHAR + + 'fiscalEndDate,' // dt:DATETIME + + 'isPubliclyTraded,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'knownAs,' // dt:VARCHAR + + 'legal,' // dt:VARCHAR + + 'name,' // dt:VARCHAR + + 'ownerId,' // dt:VARCHAR + + 'phone,' // dt:VARCHAR + + 'profitsDate,' // dt:DATETIME + + 'profitsPerYear,' // dt:INT + + 'replacementId,' // dt:VARCHAR + + 'revenue,' // dt:INT + + 'revenueDate,' // dt:DATETIME + + 'state,' // dt:VARCHAR + + 'street,' // dt:VARCHAR + + 'symbol,' // dt:VARCHAR + + 'synchronizedDate,' // dt:DATETIME + + 'tier,' // dt:INT + + 'toBeDeletedOn,' // dt:DATETIME + + 'updatedDate,' // dt:DATETIME + + 'website,' // dt:VARCHAR + + 'zip,' // dt:VARCHAR break; case 'Platform': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:DATETIME - + 'deleteReason,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'name,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:DATETIME - + 'toBeDeletedOn,' // dt:DATETIME - + 'updatedDate,' // dt:DATETIME + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:DATETIME + + 'deleteReason,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'name,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:DATETIME + + 'toBeDeletedOn,' // dt:DATETIME + + 'updatedDate,' // dt:DATETIME break; case 'SoftwareEdition': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:DATETIME - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'name,' // dt:VARCHAR - + 'order_,' // dt:INT - + 'replacementId,' // dt:INT - + 'synchronizedDate,' // dt:DATETIME - + 'toBeDeletedOn,' // dt:DATETIME - + 'updatedDate,' // dt:DATETIME - + 'softwareProduct,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:DATETIME + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'name,' // dt:VARCHAR + + 'order_,' // dt:INT + + 'replacementId,' // dt:INT + + 'synchronizedDate,' // dt:DATETIME + + 'toBeDeletedOn,' // dt:DATETIME + + 'updatedDate,' // dt:DATETIME + + 'softwareProduct,' // dt:VARCHAR break; case 'SoftwareFamily': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'name,' // dt:VARCHAR - + 'replacementId,' // dt:INT - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'manufacturer,' // dt:VARCHAR - + 'taxonomy,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'name,' // dt:VARCHAR + + 'replacementId,' // dt:INT + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'manufacturer,' // dt:VARCHAR + + 'taxonomy,' // dt:VARCHAR break; case 'SoftwareLifecycle': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'endOfLife,' // dt:VARCHAR - + 'endOfLifeCalculatedCase,' // dt:VARCHAR - + 'endOfLifeDate,' // dt:DATE - + 'endOfLifeDateCalculated,' // dt:TIMESTAMP - + 'endOfLifeException,' // dt:VARCHAR - + 'endOfLifeSupportLevel,' // dt:VARCHAR - + 'generalAvailability,' // dt:VARCHAR - + 'generalAvailabilityDate,' // dt:TIMESTAMP - + 'generalAvailabilityDateCalculated,' // dt:TIMESTAMP - + 'generalAvailabilityException,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'obsolete,' // dt:VARCHAR - + 'obsoleteCalculatedCase,' // dt:VARCHAR - + 'obsoleteDate,' // dt:TIMESTAMP - + 'obsoleteDateCalculated,' // dt:TIMESTAMP - + 'obsoleteException,' // dt:VARCHAR - + 'obsoleteSupportLevel,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'softwareRelease,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'endOfLife,' // dt:VARCHAR + + 'endOfLifeCalculatedCase,' // dt:VARCHAR + + 'endOfLifeDate,' // dt:DATE + + 'endOfLifeDateCalculated,' // dt:TIMESTAMP + + 'endOfLifeException,' // dt:VARCHAR + + 'endOfLifeSupportLevel,' // dt:VARCHAR + + 'generalAvailability,' // dt:VARCHAR + + 'generalAvailabilityDate,' // dt:TIMESTAMP + + 'generalAvailabilityDateCalculated,' // dt:TIMESTAMP + + 'generalAvailabilityException,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'obsolete,' // dt:VARCHAR + + 'obsoleteCalculatedCase,' // dt:VARCHAR + + 'obsoleteDate,' // dt:TIMESTAMP + + 'obsoleteDateCalculated,' // dt:TIMESTAMP + + 'obsoleteException,' // dt:VARCHAR + + 'obsoleteSupportLevel,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'softwareRelease,' // dt:VARCHAR break; case 'SoftwareMarketVersion': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'name,' // dt:VARCHAR - + 'order_,' // dt:INT - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'softwareProduct,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'name,' // dt:VARCHAR + + 'order_,' // dt:INT + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'softwareProduct,' // dt:VARCHAR break; case 'SoftwareProduct': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'alias,' // dt:VARCHAR - + 'application,' // dt:VARCHAR - + 'cloud,' // dt:VARCHAR - + 'component,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isFamilyInFullName,' // dt:TINYINT - + 'isSuite,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'name,' // dt:VARCHAR - + 'productLicensable,' // dt:INT - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'manufacturer,' // dt:VARCHAR - + 'softwareFamily,' // dt:VARCHAR - + 'taxonomy,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'alias,' // dt:VARCHAR + + 'application,' // dt:VARCHAR + + 'cloud,' // dt:VARCHAR + + 'component,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isFamilyInFullName,' // dt:TINYINT + + 'isSuite,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'name,' // dt:VARCHAR + + 'productLicensable,' // dt:INT + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'manufacturer,' // dt:VARCHAR + + 'softwareFamily,' // dt:VARCHAR + + 'taxonomy,' // dt:VARCHAR break; case 'SoftwareProductLink': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'cloud,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'formerSoftwareProductId,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'laterSoftwareProductId,' // dt:VARCHAR - + 'latestSoftwareProductId,' // dt:VARCHAR - + 'oldestSoftwareProductId,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'softwareCloudId,' // dt:VARCHAR - + 'softwareOnPremId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'softwareProduct,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'cloud,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'formerSoftwareProductId,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'laterSoftwareProductId,' // dt:VARCHAR + + 'latestSoftwareProductId,' // dt:VARCHAR + + 'oldestSoftwareProductId,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'softwareCloudId,' // dt:VARCHAR + + 'softwareOnPremId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'softwareProduct,' // dt:VARCHAR break; case 'SoftwareRelease': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'application,' // dt:VARCHAR - + 'cloud,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isLicensable,' // dt:TINYINT - + 'isMajor,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'majorSoftwareReleaseId,' // dt:VARCHAR - + 'name,' // dt:VARCHAR - + 'patchLevel,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'scaOpenSource,' // dt:VARCHAR - + 'softwareEdition,' // dt:VARCHAR - + 'softwareProduct,' // dt:VARCHAR - + 'softwareVersion,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'application,' // dt:VARCHAR + + 'cloud,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isLicensable,' // dt:TINYINT + + 'isMajor,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'majorSoftwareReleaseId,' // dt:VARCHAR + + 'name,' // dt:VARCHAR + + 'patchLevel,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'scaOpenSource,' // dt:VARCHAR + + 'softwareEdition,' // dt:VARCHAR + + 'softwareProduct,' // dt:VARCHAR + + 'softwareVersion,' // dt:VARCHAR break; case 'SoftwareReleaseLink': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'formerSoftwareReleaseId,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'laterSoftwareReleaseId,' // dt:VARCHAR - + 'latestSoftwareReleaseId,' // dt:VARCHAR - + 'oldestSoftwareReleaseId,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'softwareRelease,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'formerSoftwareReleaseId,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'laterSoftwareReleaseId,' // dt:VARCHAR + + 'latestSoftwareReleaseId,' // dt:VARCHAR + + 'oldestSoftwareReleaseId,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'softwareRelease,' // dt:VARCHAR break; case 'SoftwareReleasePlatform': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:TIMESTAMP - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'platformLabel,' // dt:VARCHAR - + 'platformType,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:TIMESTAMP - + 'toBeDeletedOn,' // dt:DATE - + 'updatedDate,' // dt:TIMESTAMP - + 'platform,' // dt:VARCHAR - + 'softwareRelease,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:TIMESTAMP + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'platformLabel,' // dt:VARCHAR + + 'platformType,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:TIMESTAMP + + 'toBeDeletedOn,' // dt:DATE + + 'updatedDate,' // dt:TIMESTAMP + + 'platform,' // dt:VARCHAR + + 'softwareRelease,' // dt:VARCHAR break; case 'SoftwareVersion': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'createdDate,' // dt:DATETIME - + 'deleteReason,' // dt:VARCHAR - + 'isDesupported,' // dt:TINYINT - + 'isDiscontinued,' // dt:TINYINT - + 'isMajor,' // dt:TINYINT - + 'isToBeDeleted,' // dt:TINYINT - + 'majorSoftwareVersionId,' // dt:VARCHAR - + 'name,' // dt:VARCHAR - + 'order_,' // dt:INT - + 'patchLevel,' // dt:VARCHAR - + 'replacementId,' // dt:VARCHAR - + 'synchronizedDate,' // dt:DATETIME - + 'toBeDeletedOn,' // dt:DATETIME - + 'updatedDate,' // dt:DATETIME - + 'versionStage,' // dt:VARCHAR - + 'softwareMarketVersion,' // dt:VARCHAR - + 'softwareProduct,' // dt:VARCHAR + + 'id,' // dt:VARCHAR + + 'createdDate,' // dt:DATETIME + + 'deleteReason,' // dt:VARCHAR + + 'isDesupported,' // dt:TINYINT + + 'isDiscontinued,' // dt:TINYINT + + 'isMajor,' // dt:TINYINT + + 'isToBeDeleted,' // dt:TINYINT + + 'majorSoftwareVersionId,' // dt:VARCHAR + + 'name,' // dt:VARCHAR + + 'order_,' // dt:INT + + 'patchLevel,' // dt:VARCHAR + + 'replacementId,' // dt:VARCHAR + + 'synchronizedDate,' // dt:DATETIME + + 'toBeDeletedOn,' // dt:DATETIME + + 'updatedDate,' // dt:DATETIME + + 'versionStage,' // dt:VARCHAR + + 'softwareMarketVersion,' // dt:VARCHAR + + 'softwareProduct,' // dt:VARCHAR break; case 'Taxonomy': insertStatement = insertStatement - + 'id,' // dt:VARCHAR - + 'category,' // dt:VARCHAR - + 'categoryGroup,' // dt:VARCHAR - + 'categoryId,' // dt:VARCHAR - + 'createdDate,' // dt:DATETIME - + 'deleteReason,' // dt:VARCHAR - + 'description,' // dt:VARCHAR - + 'isToBeDeleted,' // dt:TINYINT - + 'replacementId,' // dt:VARCHAR - + 'softwareOrHardware,' // dt:VARCHAR - + 'subcategory,' // dt:VARCHAR - + 'synchronizedDate,' // dt:DATETIME - + 'toBeDeletedOn,' // dt:DATETIME - + 'updatedDate,' // dt:DATETIME + + 'id,' // dt:VARCHAR + + 'category,' // dt:VARCHAR + + 'categoryGroup,' // dt:VARCHAR + + 'categoryId,' // dt:VARCHAR + + 'createdDate,' // dt:DATETIME + + 'deleteReason,' // dt:VARCHAR + + 'description,' // dt:VARCHAR + + 'isToBeDeleted,' // dt:TINYINT + + 'replacementId,' // dt:VARCHAR + + 'softwareOrHardware,' // dt:VARCHAR + + 'subcategory,' // dt:VARCHAR + + 'synchronizedDate,' // dt:DATETIME + + 'toBeDeletedOn,' // dt:DATETIME + + 'updatedDate,' // dt:DATETIME break; default: throw `dataset provide when getting insert columns list is not supported: ${datasetName}`; @@ -1801,40 +1865,7 @@ function writeToLogFile(msg, logFileName = null) { fs.appendFileSync(logFileName, msg); } - -function formatDateTime(dateObj) { - - // - description: formats the date and time for logging - // - parameters: dateObj (date object) - // - returns: formattedDate (string) 'yyyy-mm-dd hh:mm:ss.mmm' or null - - if (dateObj === null || dateObj === '') { - return null; - } else { - - let formattedDate = new Date(dateObj); - - if (formattedDate === null || formattedDate === '') { - return null; - } else { - // Get the individual date components - const year = formattedDate.getFullYear(); - const month = String(formattedDate.getMonth() + 1).padStart(2, '0'); - const day = String(formattedDate.getDate()).padStart(2, '0'); - - // Get the individual time components - const hours = String(formattedDate.getHours()).padStart(2, '0'); - const minutes = String(formattedDate.getMinutes()).padStart(2, '0'); - const seconds = String(formattedDate.getSeconds()).padStart(2, '0'); - const milliseconds = String(formattedDate.getMilliseconds()).padStart(3, '0'); - - // Combine the components into the desired format - formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; - - return formattedDate; - } - } -} +exports.formatDateTime = (dateObj) => formatDateTime(dateObj); function formatFileDateTime(dateObj) { @@ -1894,12 +1925,12 @@ function formatDuration(start_date, end_date) { } result += seconds + (seconds === 1 ? " second" : " seconds"); } - + return result; } } -function stringToDate (dateString) { +function stringToDate(dateString) { // - description: converts a date formatted string to a date object // - parameters: dateString (string) @@ -1911,14 +1942,14 @@ function stringToDate (dateString) { if (dateString.includes('T') && dateString.slice(-1) === 'Z') { dateString = dateString.replace('T', ' ').replace('Z', ''); } - + var newDate = new Date(dateString); - newDate = newDate.getFullYear() + "-" + - String(newDate.getMonth() + 1).padStart(2, '0') + "-" + - String(newDate.getDate()).padStart(2, '0') + " " + - String(newDate.getHours()).padStart(2, '0') + ":" + - String(newDate.getMinutes()).padStart(2, '0') + ":" + - String(newDate.getSeconds()).padStart(2, '0'); + newDate = newDate.getFullYear() + "-" + + String(newDate.getMonth() + 1).padStart(2, '0') + "-" + + String(newDate.getDate()).padStart(2, '0') + " " + + String(newDate.getHours()).padStart(2, '0') + ":" + + String(newDate.getMinutes()).padStart(2, '0') + ":" + + String(newDate.getSeconds()).padStart(2, '0'); if (newDate === 'NaN-NaN-NaN NaN:NaN:NaN') { return null; } else { @@ -1940,7 +1971,7 @@ function stringToDate (dateString) { } }*/ -function booleanToTinyint (boolean) { +function booleanToTinyint(boolean) { // - description: converts a boolean to a tinyint when inserting booleans into the database // - parameters: boolean (boolean) @@ -2028,7 +2059,7 @@ function getImportId(data = null) { if (data.singlerecimportidoverride) { minutes = String(formattedDate.getMinutes()).padStart(2, '0'); //seconds = String(formattedDate.getSeconds()).padStart(2, '0'); - } + } } catch (error) { minutes = ''; seconds = ''; @@ -2055,9 +2086,9 @@ exports.importTechCatlogData = async (data, response) => { const importId = getImportId(data); // import id identifies a group of import requests to be used for logging const isDryRun = data.dryrun; // flag to determine if the import insert/update statement or not const importMethod = data.importmethod ? data.importmethod : 'nowait'; // import method to be used for the performing the insert/update statement - // - 'nowait' or '' sends the insert/update statement to the database and does not wait for a response - // - 'wait' sends the insert/update statement to the database and waits for a response - + // - 'nowait' or '' sends the insert/update statement to the database and does not wait for a response + // - 'wait' sends the insert/update statement to the database and waits for a response + let lastSyncDateOverride = null; // last synchronized date to override the last sync date in the database let lastIdOverride = null; // last id to override the last id in the database let maxSyncDateOverride = null; // max sync date to override the max sync date in the database @@ -2079,7 +2110,7 @@ exports.importTechCatlogData = async (data, response) => { let softwareSupportStageCounter = 0; // counter to determine if the softwareSupportStage insert statement should be built let softwareSupportStageInsertedCounter = 0; // total number of softwareSupportStage records inserted into db let softwareSupportStageErrorCounter = 0; // total number of softwareSupportStage records that failed to insert into db - + let pageCounter = 0; // total number of pages processed let pageRequestCounter = 0; // total number of page requests made let recordCounter = 0; // total number of records received @@ -2090,7 +2121,7 @@ exports.importTechCatlogData = async (data, response) => { let recordsInsertedCounter = 0; // total number of records inserted into db let recordToUpdateCounter = 0; // total number of records to update - + let pageSummaryArray = []; // array of page summary objects, page added after completed let recordsFailedList = []; // list of records that failed to insert into db let reImportFailedRecords = false; @@ -2098,7 +2129,7 @@ exports.importTechCatlogData = async (data, response) => { let beginTableRecordCount = 0; // number of records in the table before the import process begins let endTableRecordCount = 0; // number of records in the table after the import process ends - + let isLastPage = false; // flag to determine if this is the last page let lastRecordId = null; // id of last record processed let lastRecordIdUsed = null; // the inital last id value used to start the import process @@ -2118,78 +2149,78 @@ exports.importTechCatlogData = async (data, response) => { // log file to store general import information - const importLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_import_log_${formatFileDateTime(uploadStartTime)}.log`; + const importLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_import_log_${formatFileDateTime(uploadStartTime)}.log`; // log file to store the list of records to be inserted/updated - const toSyncListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_to_sync_list_${formatFileDateTime(uploadStartTime)}.log`; + const toSyncListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_to_sync_list_${formatFileDateTime(uploadStartTime)}.log`; // log file to store the list of records that were inserted/updated - const syncedListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_synced_list_${formatFileDateTime(uploadStartTime)}.log`; + const syncedListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_synced_list_${formatFileDateTime(uploadStartTime)}.log`; // log file to store the list of records that failed to be inserted/updated - const deleteListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_to_delete_list_${formatFileDateTime(uploadStartTime)}.log`; + const deleteListLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_records_to_delete_list_${formatFileDateTime(uploadStartTime)}.log`; // log file to store the list of records that failed to be inserted/updated or any other errors during the import process - const errorLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_ERRORs_${formatFileDateTime(uploadStartTime)}.log`; + const errorLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_ERRORs_${formatFileDateTime(uploadStartTime)}.log`; // log file to store the import summary json - const importSummaryLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_import_summary_${formatFileDateTime(uploadStartTime)}.log`; + const importSummaryLogFileName = `${logFolderPath}/import_${importType}_${datasetName}_import_summary_${formatFileDateTime(uploadStartTime)}.log`; + - // ... prepares a header without the time to be put in front of each logger message - function getLogHeaderNoTime () { - return `/${importId}/${datasetName}/` - + (pageCounter > 0 ? `p${pageCounter}/` - + (recordCountDisplay > 0 ? `r${recordCountDisplay}/` : '') : ''); + function getLogHeaderNoTime() { + return `/${importId}/${datasetName}/` + + (pageCounter > 0 ? `p${pageCounter}/` + + (recordCountDisplay > 0 ? `r${recordCountDisplay}/` : '') : ''); } // ... prepares a header to be put in front of each logger message - function getLogHeader () { + function getLogHeader() { return `${formatDateTime(new Date())}${getLogHeaderNoTime()}`; } // ... returns the import summary at any point in the import process - function getImportSummary () { + function getImportSummary() { const summary = { - message : `Technopedia Data Import`, - importId : importId, - importType : importType, - dataset : datasetName, - takeAmount : takeAmt, - dryRun : (isDryRun === 'true' ? 'true' : 'false'), - lastSyncDateOverride : formatDateTime(lastSyncDateOverride), - maxSyncDateOverride : formatDateTime(maxSyncDateOverride), - lastIdOverride : lastIdOverride, - singleRecImportIdOverride : singleRecImportId, - isToBeDeletedRecordsOnly : isToBeDeletedOnly, - lastRecordId : lastRecordId, - firstAfterIdUsed : lastRecordIdUsed, - lastSynchronizedDateUsed : formatDateTime(lastSynchronizedDate), - startTime : formatDateTime(uploadStartTime), - endTime : formatDateTime(uploadEndTime), - duration : formatDuration(uploadStartTime, uploadEndTime), - totalPageRequestsMade : pageRequestCounter, - totalPages : pageCounter, - totalRecords : recordCounter, - totalRecordsToBeInsertedUpdated : recordToUpdateCounter, - totalRecordsInsertedUpdated : recordsInsertedCounter, - totalRecordsFailed : recordsFailedCounter, - totalSoftwareSupportStageRecords : softwareSupportStageCounter, - totalSoftwareSupportStageRecordsInsUpd : softwareSupportStageInsertedCounter, - totalSoftwareSupportStageRecordsFailed : softwareSupportStageErrorCounter, - beginTableRecordCount : beginTableRecordCount, - endTableRecordCount : endTableRecordCount, - affectRowsCounter1 : affectRowsCounter1, - affectRowsCounter2 : affectRowsCounter2, - affectRowsCounter3 : affectRowsCounter3, - fatalError : isFatalError, - logDateTime : formatDateTime(new Date()), - pageSummaries : "see logs", - deletedRecsNotRemovedCounter : deletedRecsNotRemovedCounter, - recordsToBeDeleted : "see logs", - earliestSyncDate : (earliestSyncDate === null ? null : formatDateTime(earliestSyncDate)), - latestSyncDate : formatDateTime(latestSyncDate), - syncYYYYMMDDArray : `see tech_catalog.dataset_syncdate_log table`, - isGatheringStats : gatherStats, - failedRecordsList : recordsFailedList + message: `Technopedia Data Import`, + importId: importId, + importType: importType, + dataset: datasetName, + takeAmount: takeAmt, + dryRun: (isDryRun === 'true' ? 'true' : 'false'), + lastSyncDateOverride: formatDateTime(lastSyncDateOverride), + maxSyncDateOverride: formatDateTime(maxSyncDateOverride), + lastIdOverride: lastIdOverride, + singleRecImportIdOverride: singleRecImportId, + isToBeDeletedRecordsOnly: isToBeDeletedOnly, + lastRecordId: lastRecordId, + firstAfterIdUsed: lastRecordIdUsed, + lastSynchronizedDateUsed: formatDateTime(lastSynchronizedDate), + startTime: formatDateTime(uploadStartTime), + endTime: formatDateTime(uploadEndTime), + duration: formatDuration(uploadStartTime, uploadEndTime), + totalPageRequestsMade: pageRequestCounter, + totalPages: pageCounter, + totalRecords: recordCounter, + totalRecordsToBeInsertedUpdated: recordToUpdateCounter, + totalRecordsInsertedUpdated: recordsInsertedCounter, + totalRecordsFailed: recordsFailedCounter, + totalSoftwareSupportStageRecords: softwareSupportStageCounter, + totalSoftwareSupportStageRecordsInsUpd: softwareSupportStageInsertedCounter, + totalSoftwareSupportStageRecordsFailed: softwareSupportStageErrorCounter, + beginTableRecordCount: beginTableRecordCount, + endTableRecordCount: endTableRecordCount, + affectRowsCounter1: affectRowsCounter1, + affectRowsCounter2: affectRowsCounter2, + affectRowsCounter3: affectRowsCounter3, + fatalError: isFatalError, + logDateTime: formatDateTime(new Date()), + pageSummaries: "see logs", + deletedRecsNotRemovedCounter: deletedRecsNotRemovedCounter, + recordsToBeDeleted: "see logs", + earliestSyncDate: (earliestSyncDate === null ? null : formatDateTime(earliestSyncDate)), + latestSyncDate: formatDateTime(latestSyncDate), + syncYYYYMMDDArray: `see tech_catalog.dataset_syncdate_log table`, + isGatheringStats: gatherStats, + failedRecordsList: recordsFailedList }; return summary; } // ... - function updateImportLog (logMessage) { + function updateImportLog(logMessage) { try { let importLogStatement = null; @@ -2249,7 +2280,7 @@ exports.importTechCatlogData = async (data, response) => { logger(`${getLogHeader()}`, `failed to update import log`, err, errorLogFileName); } }); - + } catch (error) { logger(`${getLogHeader()}`, `an unexpected error occurred during updateImportLog()`, error, errorLogFileName); } @@ -2266,7 +2297,7 @@ exports.importTechCatlogData = async (data, response) => { // ... if fatalError or duplicateJobsRunning if (isFatalError === 1) { logger(`${getLogHeader()}`, `ENDING IMPORT DUE TO FATAL ERROR`, null, importLogFileName); - } else if (duplicateJobsRunning === 1) { + } else if (duplicateJobsRunning === 1) { logger(`${getLogHeader()}`, `ENDING IMPORT DUE TO DUPLICATE JOB RUNNING`, null, importLogFileName); } @@ -2329,7 +2360,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { logger(`${getLogHeader()}`, `an unexpected error occurred during endImport()`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return error; } @@ -2341,7 +2372,7 @@ exports.importTechCatlogData = async (data, response) => { // import process try { - + // ----------------------------------------------- // pre-import steps try { @@ -2364,17 +2395,17 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed validating request parameters`, error, errorLogFileName); - return { message : `ERROR: Tech Catalog Import Request Parameters Contains Invalid Values:\n${error}`, fatalError : 1 }; + return { message: `ERROR: Tech Catalog Import Request Parameters Contains Invalid Values:\n${error}`, fatalError: 1 }; } // 1. log start to tech_catalog.dataset_import_log try { // log start to db - await sql_promise.query(`insert into tech_catalog.dataset_import_log (import_id, datasetName, import_status) values ('${importId}', '${datasetName}', '${datasetName} import in progress...'); `); + await connPromisePool.query(`insert into tech_catalog.dataset_import_log (import_id, datasetName, import_status) values ('${importId}', '${datasetName}', '${datasetName} import in progress...'); `); } catch (er) { console.log(`\n****** ${datasetName} import is already in progress ******\n`); //, er); - duplicateJobsRunning=1; - return { message : `${datasetName} import is already in progress`, fatalError : 0, duplicateJobsRunning : duplicateJobsRunning }; + duplicateJobsRunning = 1; + return { message: `${datasetName} import is already in progress`, fatalError: 0, duplicateJobsRunning: duplicateJobsRunning }; } // ----------------------------------------------- @@ -2404,11 +2435,11 @@ exports.importTechCatlogData = async (data, response) => { if (!singleRecImportId) { // handle OVERRIDE values - + // check lastIdOverride exists try { if (data.lastidoverride) { - lastRecordId = lastRecordIdUsed = lastIdOverride = data.lastidoverride; + lastRecordId = lastRecordIdUsed = lastIdOverride = data.lastidoverride; // turn off stats gathering for imports using last id overrides gatherStats = false; @@ -2474,7 +2505,7 @@ exports.importTechCatlogData = async (data, response) => { // get last synchronizedDate if (!lastSynchronizedDate) { // ... get the last synchronizedDate from the database - lastSynchronizedDate = await getLastSyncDate(tableName, getLogHeaderNoTime()); + lastSynchronizedDate = await getLastSyncDate(tableName, getLogHeaderNoTime()); lastSynchronizedDate.setHours(0, 0, 0, 0); } @@ -2485,7 +2516,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed getting the last id and synchronizedDate`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -2495,7 +2526,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log error, end import. logger(`${getLogHeader()}`, `an unexpected error occurred during pre-import steps`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } // end pre-import -------------------------------- @@ -2532,23 +2563,23 @@ exports.importTechCatlogData = async (data, response) => { let datasetArray = []; // ... returns the page summary at any point in the import process - function getPageSummary () { + function getPageSummary() { pageEndTime = new Date(); const summary = { - importId : importId, - page : pageCounter, - startTime : formatDateTime(pageStartTime), - endTime : formatDateTime(pageEndTime), - duration : formatDuration(pageStartTime, pageEndTime), - arraySize : datasetArray.length, - pageRecordsProcessed : pageRecordCounter, - pageRecordsInserted : pageRecordsInsertedCounter, - pageRecordsToUpdate : pageRecordsToUpdateCounter, - pageRecordsFailed : pageRecordsFailedCounter, - consecutiveFailedRecords : consecutiveFailedRecordCounter, - isLastPage : isLastPage, - logDateTime : formatDateTime(new Date()) - }; + importId: importId, + page: pageCounter, + startTime: formatDateTime(pageStartTime), + endTime: formatDateTime(pageEndTime), + duration: formatDuration(pageStartTime, pageEndTime), + arraySize: datasetArray.length, + pageRecordsProcessed: pageRecordCounter, + pageRecordsInserted: pageRecordsInsertedCounter, + pageRecordsToUpdate: pageRecordsToUpdateCounter, + pageRecordsFailed: pageRecordsFailedCounter, + consecutiveFailedRecords: consecutiveFailedRecordCounter, + isLastPage: isLastPage, + logDateTime: formatDateTime(new Date()) + }; pageSummaryArray.push(summary); return summary; } @@ -2582,7 +2613,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed getting the access token`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -2600,24 +2631,24 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed building the graphql query`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } - + // ----------------------------------------------- // 3. execute page request try { try { - + // ... sending api request for page data to flexera pageJson = await sendAPIQuery(graphqlQuery, accessToken, getLogHeaderNoTime()); } catch (error) { logger(`${getLogHeader()}`, `WARNING: Page ${pageCounter} Data API request failed, waiting 30 seconds and will try again\n>>>>ERROR: ${error}`, null, importLogFileName); - + // wait 30 sec and try again await new Promise(resolve => setTimeout(resolve, 30000)); pageJson = await sendAPIQuery(graphqlQuery, accessToken, getLogHeaderNoTime()); @@ -2628,11 +2659,11 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed executing page request`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } - - + + // ----------------------------------------------- // 4. verifying page response try { @@ -2644,7 +2675,7 @@ exports.importTechCatlogData = async (data, response) => { // ... check if dataset array is empty if (datasetArray.length === 0) { - + // ... if array is empty, // ... logging no records returned logger(`${getLogHeader()}`, `... no records returned from flexera api for this page`); @@ -2661,11 +2692,11 @@ exports.importTechCatlogData = async (data, response) => { logger(`${getLogHeader()}`, `... ${datasetArray.length} records received`); } - + } catch (error) { // ... log failure, end import. logger(`${getLogHeader()}`, `failed verifying api response`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -2673,7 +2704,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log error, end import. logger(`${getLogHeader()}`, `unexpected error occurred during page request`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -2710,23 +2741,23 @@ exports.importTechCatlogData = async (data, response) => { if (maxSyncDateOverride !== null && datasetArray.length > 0) { const previousRecordCount = datasetArray.length; - + datasetArray = datasetArray.filter(datasetArray => new Date(stringToDate(datasetArray.synchronizedDate)) <= maxSyncDateOverride); - + const filteredRecordCount = datasetArray.length; filteredRecordsCounter = filteredRecordsCounter + (previousRecordCount - filteredRecordCount); - + logger(`${getLogHeader()}`, `... *** ${previousRecordCount - filteredRecordCount} records removed *** from page array because synchronizedDate > maxSyncDateOverride`, null, importLogFileName); } - + if (filteredRecordsCounter > 0) { logger(`${getLogHeader()}`, `... *** ${datasetArray.length} records to import after filtering ***`, null, importLogFileName); } } - - + + // ----------------------------------------------- // records process for (let datasetObject of datasetArray) { @@ -2775,7 +2806,7 @@ exports.importTechCatlogData = async (data, response) => { // if gathering stats in turned on (only for imports with no override values) if (gatherStats) { // gathering counts of records by sync date to insert into tech_catalog.dataset_syncdate_log - let recordSynchronizedDateYYYYMMDD = recordSynchronizedDate.getFullYear() + '-' + String(recordSynchronizedDate.getMonth() + 1).padStart(2, '0') + '-' + String(recordSynchronizedDate.getDate()).padStart(2, '0'); + let recordSynchronizedDateYYYYMMDD = recordSynchronizedDate.getFullYear() + '-' + String(recordSynchronizedDate.getMonth() + 1).padStart(2, '0') + '-' + String(recordSynchronizedDate.getDate()).padStart(2, '0'); // ... check if recordSynchronizedDateYYYYMMDD exists in syncYYYYMMDDArray let recordSynchronizedDateYYYYMMDDObject = syncYYYYMMDDArray.find(syncYYYYMMDDArray => String(syncYYYYMMDDArray.syncDate) === recordSynchronizedDateYYYYMMDD); @@ -2784,7 +2815,7 @@ exports.importTechCatlogData = async (data, response) => { recordSynchronizedDateYYYYMMDDObject.recordCount++; } else { // ... add the record to the array - syncYYYYMMDDArray.push({ import_id : importId, datasetName : datasetName, syncDate : recordSynchronizedDateYYYYMMDD, recordCount : 1 }); + syncYYYYMMDDArray.push({ import_id: importId, datasetName: datasetName, syncDate: recordSynchronizedDateYYYYMMDD, recordCount: 1 }); } } @@ -2823,9 +2854,9 @@ exports.importTechCatlogData = async (data, response) => { try { // ... create record object - let insertTxt = + let insertTxt = { - id : lastRecordId + id: lastRecordId // synchronizedDate : datasetObject.synchronizedDate }; @@ -2835,7 +2866,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { logger(`${getLogHeader()}`, `failed writing record to sync list log`, error, errorLogFileName); } - + // ... increment the counters when a record to update is found recordToUpdateCounter++; @@ -2869,22 +2900,22 @@ exports.importTechCatlogData = async (data, response) => { } else { logger(`${getLogHeader()}`, `...... id:"${lastRecordId}" will be deleted in the near future`); } - - let deleteTxt = + + let deleteTxt = { - id : lastRecordId, - synchronizedDate : datasetObject.synchronizedDate, - isToBeDeleted : datasetObject.isToBeDeleted, - toBeDeletedOn : datasetObject.toBeDeletedOn, - deleteReason : datasetObject.deleteReason, - deletedNotRemoved : deletedNotRemoved + id: lastRecordId, + synchronizedDate: datasetObject.synchronizedDate, + isToBeDeleted: datasetObject.isToBeDeleted, + toBeDeletedOn: datasetObject.toBeDeletedOn, + deleteReason: datasetObject.deleteReason, + deletedNotRemoved: deletedNotRemoved }; // ... write record to sync list log fs.appendFileSync(deleteListLogFileName, JSON.stringify(deleteTxt) + ',\n'); recordsToBeDeletedArray.push(deleteTxt); - + // for SoftwareRelease, updating obj_technology table ToBeDelete columns if (datasetName === 'SoftwareRelease') { try { @@ -2940,7 +2971,7 @@ exports.importTechCatlogData = async (data, response) => { } else { // ... raise error throw `importType value is invalid`; - } + } // ----------------------------------------------- @@ -2949,7 +2980,7 @@ exports.importTechCatlogData = async (data, response) => { // ----------------------------------------------- // 1. build insert/update statement - try{ + try { // ... add the record object to array recordsToInsert.push(datasetObject); @@ -2957,153 +2988,153 @@ exports.importTechCatlogData = async (data, response) => { // ... add the column values to map switch (datasetName) { case 'Manufacturer': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.acquiredDate, - datasetObject.city, - datasetObject.country, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.description, - datasetObject.email, - datasetObject.employees, - stringToDate(datasetObject.employeesDate), - datasetObject.fax, - stringToDate(datasetObject.fiscalEndDate), - datasetObject.isPubliclyTraded, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.knownAs, - datasetObject.legal, - datasetObject.name, - datasetObject.ownerId, - datasetObject.phone, - stringToDate(datasetObject.profitsDate), - datasetObject.profitsPerYear, - datasetObject.replacementId, - datasetObject.revenue, - stringToDate(datasetObject.revenueDate), - datasetObject.state, - datasetObject.street, - datasetObject.symbol, - stringToDate(datasetObject.synchronizedDate), - datasetObject.tier, - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.website, - datasetObject.zip, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.acquiredDate, + datasetObject.city, + datasetObject.country, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.description, + datasetObject.email, + datasetObject.employees, + stringToDate(datasetObject.employeesDate), + datasetObject.fax, + stringToDate(datasetObject.fiscalEndDate), + datasetObject.isPubliclyTraded, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.knownAs, + datasetObject.legal, + datasetObject.name, + datasetObject.ownerId, + datasetObject.phone, + stringToDate(datasetObject.profitsDate), + datasetObject.profitsPerYear, + datasetObject.replacementId, + datasetObject.revenue, + stringToDate(datasetObject.revenueDate), + datasetObject.state, + datasetObject.street, + datasetObject.symbol, + stringToDate(datasetObject.synchronizedDate), + datasetObject.tier, + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.website, + datasetObject.zip, + ]); break; case 'Platform': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + ]); break; case 'SoftwareEdition': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.order, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareProduct.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.order, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareProduct.id, + ]); break; case 'SoftwareFamily': try { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.manufacturer.id, - datasetObject.taxonomy.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.manufacturer.id, + datasetObject.taxonomy.id, + ]); } catch (error) { try { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.manufacturer.id, - datasetObject.taxonomy, - ]); - } catch (error) { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.manufacturer, - datasetObject.taxonomy, + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.manufacturer.id, + datasetObject.taxonomy, ]); - } + } catch (error) { + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.manufacturer, + datasetObject.taxonomy, + ]); + } } break; case 'SoftwareLifecycle': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.endOfLife, - datasetObject.endOfLifeCalculatedCase, - stringToDate(datasetObject.endOfLifeDate), - stringToDate(datasetObject.endOfLifeDateCalculated), - datasetObject.endOfLifeException, - datasetObject.endOfLifeSupportLevel, - datasetObject.generalAvailability, - stringToDate(datasetObject.generalAvailabilityDate), - stringToDate(datasetObject.generalAvailabilityDateCalculated), - datasetObject.generalAvailabilityException, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.obsolete, - datasetObject.obsoleteCalculatedCase, - stringToDate(datasetObject.obsoleteDate), - stringToDate(datasetObject.obsoleteDateCalculated), - datasetObject.obsoleteException, - datasetObject.obsoleteSupportLevel, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareRelease.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.endOfLife, + datasetObject.endOfLifeCalculatedCase, + stringToDate(datasetObject.endOfLifeDate), + stringToDate(datasetObject.endOfLifeDateCalculated), + datasetObject.endOfLifeException, + datasetObject.endOfLifeSupportLevel, + datasetObject.generalAvailability, + stringToDate(datasetObject.generalAvailabilityDate), + stringToDate(datasetObject.generalAvailabilityDateCalculated), + datasetObject.generalAvailabilityException, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.obsolete, + datasetObject.obsoleteCalculatedCase, + stringToDate(datasetObject.obsoleteDate), + stringToDate(datasetObject.obsoleteDateCalculated), + datasetObject.obsoleteException, + datasetObject.obsoleteSupportLevel, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareRelease.id, + ]); // check if softwareSupportStage has data if (datasetObject.softwareSupportStage !== null && datasetObject.softwareSupportStage !== undefined) { @@ -3118,21 +3149,21 @@ exports.importTechCatlogData = async (data, response) => { } } - for (let supportStageObject of softwareSupportStageArray) { + for (let supportStageObject of softwareSupportStageArray) { // create insert statement let insertStatementSSS = insertStatementSftwSupportStage + `'${datasetObject.id}', ` + `'${formatDateTime(String(datasetObject.synchronizedDate).replace('T', ' ').replace('Z', ''))}', ` + - `${supportStageObject.definition === null || supportStageObject.definition === '' ? 'null' : "'"+String(supportStageObject.definition).replace(/'/g, "''").replace(/\n/g, "").replace(/\r/g, "\\r").replace(/\t/g, "\\t")+"'"}, ` + - `${supportStageObject.endDate === null || supportStageObject.endDate === '' ? 'null' : "'"+formatDateTime(String(new Date(supportStageObject.endDate)))+"'"}, ` + - `${supportStageObject.manufacturerId === null || supportStageObject.manufacturerId === '' ? 'null' : "'"+supportStageObject.manufacturerId+"'"}, ` + + `${supportStageObject.definition === null || supportStageObject.definition === '' ? 'null' : "'" + String(supportStageObject.definition).replace(/'/g, "''").replace(/\n/g, "").replace(/\r/g, "\\r").replace(/\t/g, "\\t") + "'"}, ` + + `${supportStageObject.endDate === null || supportStageObject.endDate === '' ? 'null' : "'" + formatDateTime(String(new Date(supportStageObject.endDate))) + "'"}, ` + + `${supportStageObject.manufacturerId === null || supportStageObject.manufacturerId === '' ? 'null' : "'" + supportStageObject.manufacturerId + "'"}, ` + `'${supportStageObject.name}', ` + - `${supportStageObject.order === null || supportStageObject.order === '' ? 'null' : "'"+supportStageObject.order+"'"}, ` + - `${supportStageObject.policy === null || supportStageObject.policy === '' ? 'null' : "'"+supportStageObject.policy+"'"}, ` + - `${supportStageObject.publishedEndDate === null || supportStageObject.publishedEndDate === '' ? 'null' : "'"+supportStageObject.publishedEndDate+"'"})`; + `${supportStageObject.order === null || supportStageObject.order === '' ? 'null' : "'" + supportStageObject.order + "'"}, ` + + `${supportStageObject.policy === null || supportStageObject.policy === '' ? 'null' : "'" + supportStageObject.policy + "'"}, ` + + `${supportStageObject.publishedEndDate === null || supportStageObject.publishedEndDate === '' ? 'null' : "'" + supportStageObject.publishedEndDate + "'"})`; // add the ON DUPLICATE KEY UPDATE clause to update the record if it already exists - insertStatementSSS = insertStatementSSS + ' ON DUPLICATE KEY UPDATE ' + + insertStatementSSS = insertStatementSSS + ' ON DUPLICATE KEY UPDATE ' + // PK 'id = VALUES(id), ' + 'synchronizedDate = VALUES(synchronizedDate), ' + 'definition = VALUES(definition), ' + @@ -3145,7 +3176,7 @@ exports.importTechCatlogData = async (data, response) => { // add semi-colon to end of insert statement insertStatementSSS = insertStatementSSS + '; '; - + //console.log('INSERT STATEMENT: ' + insertStatementSSS + '\n'); // testing // add insert statement to insertValuesMapSftwSupportStage @@ -3157,98 +3188,98 @@ exports.importTechCatlogData = async (data, response) => { } break; case 'SoftwareMarketVersion': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.order, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareProduct.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.order, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareProduct.id, + ]); break; case 'SoftwareProduct': - if (datasetObject.softwareFamily === null){ + if (datasetObject.softwareFamily === null) { datasetObject.softwareFamily = null; } else { datasetObject.softwareFamily = datasetObject.softwareFamily.id; } - if (datasetObject.taxonomy === null){ + if (datasetObject.taxonomy === null) { datasetObject.taxonomy = null; } else { datasetObject.taxonomy = datasetObject.taxonomy.id; } - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.alias, - datasetObject.application, - datasetObject.cloud, - datasetObject.component, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isFamilyInFullName), - booleanToTinyint(datasetObject.isSuite), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.name, - datasetObject.productLicensable, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.manufacturer.id, - datasetObject.softwareFamily, - datasetObject.taxonomy, + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.alias, + datasetObject.application, + datasetObject.cloud, + datasetObject.component, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isFamilyInFullName), + booleanToTinyint(datasetObject.isSuite), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.name, + datasetObject.productLicensable, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.manufacturer.id, + datasetObject.softwareFamily, + datasetObject.taxonomy, ]); break; case 'SoftwareProductLink': try { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.cloud, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.formerSoftwareProductId, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.laterSoftwareProductId, - datasetObject.latestSoftwareProductId, - datasetObject.oldestSoftwareProductId, - datasetObject.replacementId, - datasetObject.softwareCloudId, - datasetObject.softwareOnPremId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareProduct.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.cloud, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.formerSoftwareProductId, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.laterSoftwareProductId, + datasetObject.latestSoftwareProductId, + datasetObject.oldestSoftwareProductId, + datasetObject.replacementId, + datasetObject.softwareCloudId, + datasetObject.softwareOnPremId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareProduct.id, + ]); } catch (error) { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.cloud, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.formerSoftwareProductId, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.laterSoftwareProductId, - datasetObject.latestSoftwareProductId, - datasetObject.oldestSoftwareProductId, - datasetObject.replacementId, - datasetObject.softwareCloudId, - datasetObject.softwareOnPremId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareProduct, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.cloud, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.formerSoftwareProductId, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.laterSoftwareProductId, + datasetObject.latestSoftwareProductId, + datasetObject.oldestSoftwareProductId, + datasetObject.replacementId, + datasetObject.softwareCloudId, + datasetObject.softwareOnPremId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareProduct, + ]); } break; case 'SoftwareRelease': @@ -3273,134 +3304,134 @@ exports.importTechCatlogData = async (data, response) => { datasetObject.softwareVersion = datasetObject.softwareVersion.id; } - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.application, - datasetObject.cloud, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isLicensable), - booleanToTinyint(datasetObject.isMajor), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.majorSoftwareReleaseId, - datasetObject.name, - datasetObject.patchLevel, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.scaOpenSource, - datasetObject.softwareEdition, - datasetObject.softwareProduct.id, - datasetObject.softwareVersion, - ]); - + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.application, + datasetObject.cloud, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isLicensable), + booleanToTinyint(datasetObject.isMajor), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.majorSoftwareReleaseId, + datasetObject.name, + datasetObject.patchLevel, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.scaOpenSource, + datasetObject.softwareEdition, + datasetObject.softwareProduct.id, + datasetObject.softwareVersion, + ]); + break; case 'SoftwareReleaseLink': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.formerSoftwareReleaseId, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.laterSoftwareReleaseId, - datasetObject.latestSoftwareReleaseId, - datasetObject.oldestSoftwareReleaseId, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.softwareRelease.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.formerSoftwareReleaseId, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.laterSoftwareReleaseId, + datasetObject.latestSoftwareReleaseId, + datasetObject.oldestSoftwareReleaseId, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.softwareRelease.id, + ]); break; case 'SoftwareReleasePlatform': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.platformLabel, - datasetObject.platformType, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.platform.id, - datasetObject.softwareRelease.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.platformLabel, + datasetObject.platformType, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.platform.id, + datasetObject.softwareRelease.id, + ]); break; case 'SoftwareVersion': try { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isMajor), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.majorSoftwareVersionId, - datasetObject.name, - datasetObject.order, - datasetObject.patchLevel, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.versionStage, - datasetObject.softwareMarketVersion.id, - datasetObject.softwareProduct.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isMajor), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.majorSoftwareVersionId, + datasetObject.name, + datasetObject.order, + datasetObject.patchLevel, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.versionStage, + datasetObject.softwareMarketVersion.id, + datasetObject.softwareProduct.id, + ]); } catch (error) { - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - booleanToTinyint(datasetObject.isDesupported), - booleanToTinyint(datasetObject.isDiscontinued), - booleanToTinyint(datasetObject.isMajor), - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.majorSoftwareVersionId, - datasetObject.name, - datasetObject.order, - datasetObject.patchLevel, - datasetObject.replacementId, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - datasetObject.versionStage, - datasetObject.softwareMarketVersion, - datasetObject.softwareProduct.id, - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + booleanToTinyint(datasetObject.isDesupported), + booleanToTinyint(datasetObject.isDiscontinued), + booleanToTinyint(datasetObject.isMajor), + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.majorSoftwareVersionId, + datasetObject.name, + datasetObject.order, + datasetObject.patchLevel, + datasetObject.replacementId, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + datasetObject.versionStage, + datasetObject.softwareMarketVersion, + datasetObject.softwareProduct.id, + ]); } break; case 'Taxonomy': - insertValuesMap = recordsToInsert.map(recordsToInsert => - [datasetObject.id, - datasetObject.category, - datasetObject.categoryGroup, - datasetObject.categoryId, - stringToDate(datasetObject.createdDate), - datasetObject.deleteReason, - datasetObject.description, - booleanToTinyint(datasetObject.isToBeDeleted), - datasetObject.replacementId, - datasetObject.softwareOrHardware, - datasetObject.subcategory, - stringToDate(datasetObject.synchronizedDate), - stringToDate(datasetObject.toBeDeletedOn), - stringToDate(datasetObject.updatedDate), - ]); + insertValuesMap = recordsToInsert.map(recordsToInsert => + [datasetObject.id, + datasetObject.category, + datasetObject.categoryGroup, + datasetObject.categoryId, + stringToDate(datasetObject.createdDate), + datasetObject.deleteReason, + datasetObject.description, + booleanToTinyint(datasetObject.isToBeDeleted), + datasetObject.replacementId, + datasetObject.softwareOrHardware, + datasetObject.subcategory, + stringToDate(datasetObject.synchronizedDate), + stringToDate(datasetObject.toBeDeletedOn), + stringToDate(datasetObject.updatedDate), + ]); break; } // ... check if this is the first record on the first page for an import request if (!isStatementBuilt) { - + // ... get the list of columns for the insert statement insertStatement = insertStatement + getInsertColumnsList(datasetName); @@ -3416,7 +3447,7 @@ exports.importTechCatlogData = async (data, response) => { insertStatement = insertStatement + ') values ?'; if (importType === 'update') { - + // add ON DUPLICATE KEY UPDATE insertStatement = insertStatement + ' ON DUPLICATE KEY UPDATE '; @@ -3461,8 +3492,8 @@ exports.importTechCatlogData = async (data, response) => { await new Promise(resolve => setTimeout(resolve, 1)); } - lastInsertedUpdatedRecNum = pageRecordCounter+1; - + lastInsertedUpdatedRecNum = pageRecordCounter + 1; + // ... execute insert/update statement sql.query(insertStatement, [insertValuesMap], (error, data) => { @@ -3497,7 +3528,7 @@ exports.importTechCatlogData = async (data, response) => { // write to syncedListLogFileName file try { // ... create record object - let insertTxt = { id : idValue }; + let insertTxt = { id: idValue }; // ... write record to sync list log fs.appendFileSync(syncedListLogFileName, JSON.stringify(insertTxt) + ',\n'); @@ -3552,7 +3583,7 @@ exports.importTechCatlogData = async (data, response) => { logger(`${getLogHeader()}`, `ERROR: failed executing the insert ${insertValuesMapSftwSupportStage.length} SoftwareLifecycle.softwareSupportStage records for id: ${idValue}`, error, errorLogFileName); // ... assemble the error log insert statement let errorLogInsert = `insert into tech_catalog.dataset_record_error_log (import_id, datasetName, id, import_error) values ('${importId}', '${datasetName}', '${idValue}', '${error}'); `; - + // ... execute insert statement sql.query(errorLogInsert, (error, data) => { if (error) { @@ -3598,7 +3629,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log error, end import. logger(`${getLogHeader()}`, `an unexpected error occurred while processing the page records`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -3607,7 +3638,7 @@ exports.importTechCatlogData = async (data, response) => { // part 3: summarize page imported try { - if (!isLastPage) { + if (!isLastPage) { logger(`${getLogHeader()}`, `... summarizing page ${pageCounter} data imported`); @@ -3619,13 +3650,13 @@ exports.importTechCatlogData = async (data, response) => { // ... log page summary logger(`${getLogHeader()}`, `... page summary logged`); - + } // end of !isLastPage } catch (error) { // ... log failure logger(`${getLogHeader()}`, `an unexpected error occurred while calculating and logging page summary`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } @@ -3639,7 +3670,7 @@ exports.importTechCatlogData = async (data, response) => { // ... if the data returned is less than the takeAmt, then this is the last page - if ((datasetArray.length+filteredRecordsCounter) != takeAmt) { + if ((datasetArray.length + filteredRecordsCounter) != takeAmt) { isLastPage = true; logger(`${getLogHeader()}`, `... last page reached`); } @@ -3650,7 +3681,7 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log error, end import. logger(`${getLogHeader()}`, `an unexpected error occurred during the import steps`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } // end import steps ------------------------------ @@ -3665,13 +3696,13 @@ exports.importTechCatlogData = async (data, response) => { // ----------------------------------------------- // 1. calc, log summary, and complete import request - + return endImport(); } catch (error) { // ... log error, end import. logger(`${getLogHeader()}`, `an unexpected error occurred during the post-import steps`, error, errorLogFileName); - isFatalError=1; + isFatalError = 1; return endImport(); } // end post-import ------------------------------- @@ -3679,9 +3710,9 @@ exports.importTechCatlogData = async (data, response) => { } catch (error) { // ... log error, end import. - logger(`${getLogHeader()}`, `an unexpected error occurred during the import process`, error, errorLogFileName); - isFatalError=1; - return endImport(); + logger(`${getLogHeader()}`, `an unexpected error occurred during the import process`, error, errorLogFileName); + isFatalError = 1; + return endImport(); } // end import process ----------------------------- diff --git a/api/controllers/cron.controller.js b/api/controllers/cron.controller.js index 6407c28e..d7475b69 100644 --- a/api/controllers/cron.controller.js +++ b/api/controllers/cron.controller.js @@ -1,42 +1,55 @@ const ctrl = require('./base.controller'), - records = require('./records.controller'), techCatImport = require('./tech-catalog-import.controller'), - touchpointImport= require('./touchpoint-import.controller'), - fs = require('fs'), - path = require('path'), - queryPath = '../queries/' - SHEET_ID = '1eSoyn7-2EZMqbohzTMNDcFmNBbkl-CzXtpwNXHNHD1A', // FOR PRODUCTION - RANGE = 'Master Junction with Business Systems!A2:B', - jobUser = 'GearCronJ'; + cronJobDbUtilService = require('../cron-jobs/cron-job-db-util.service.js'), + JobLogger = require('../cron-jobs/job-logger.js'), + JobStatus = require('../enums/job-status.js'); +const SHEET_ID = '1eSoyn7-2EZMqbohzTMNDcFmNBbkl-CzXtpwNXHNHD1A', // FOR PRODUCTION +RANGE = 'Master Junction with Business Systems!A2:B', +jobUser = 'GearCronJ'; // ------------------------------------------------------------------------------------------------- // CRON JOB: Google Sheets API - Update All Related Records exports.runUpdateAllRelatedRecordsJob = async () => { - + const jobType = "RELATED-RECORDS-JOB"; const jobName = `CRON JOB: Update All Related Records`; let status = 'executed'; - + const jobLogger = new JobLogger(); + let jobId; try { + const pendingJobId = await cronJobDbUtilService.getAnyPendingJob(jobType); + if (pendingJobId) { + jobLogger.log(`Active Job '${pendingJobId}' is Running. Aborting the job now.`); + await cronJobDbUtilService.insertDbData({ jobType, startTime: ctrl.formatDateTime(new Date()), jobLogs: jobLogger.getLogs(), jobStatus: JobStatus.CANCELLED }) + return; + } let res = {}; - + + jobId = await cronJobDbUtilService.insertDbData({ jobType, startTime: ctrl.formatDateTime(new Date()), jobLogs: '', jobStatus: JobStatus.PENDING }); + console.log(jobId); + // log start of job - console.log(jobName + ' - ' + status); + jobLogger.log(jobName + ' - Execution start'); - // log start of job to db - records.logEvent({ body : { message: jobName + ' - ' + status, user: jobUser, } }, res); - // run refreshAllSystems - ctrl.googleMain(res, 'refresh', SHEET_ID, RANGE, jobUser); - + await ctrl.googleMain(res, 'refresh', SHEET_ID, RANGE, jobUser, null, jobLogger, jobId, postprocesJobExecution); } catch (error) { // log any errors status = `error occurred while running: \n` + error; - console.log(jobName + ' - ' + status); + if (jobId) { + jobLogger.log(jobName + ' - ' + status); + jobLogger.log(error.stack); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } else { + jobLogger.log(error); + } } }; +async function postprocesJobExecution(jobId, jobLogger, jobStatus) { + await cronJobDbUtilService.updateDbData({ jobStatus: jobStatus, endTime: ctrl.formatDateTime(new Date()), jobLogs: jobLogger.getLogs(), jobId: jobId }) +} // ------------------------------------------------------------------------------------------------- // CRON JOB: Tech Catalog Daily Import (runs daily at 5:00 AM) exports.runTechCatalogImportJob = async () => { @@ -54,15 +67,15 @@ exports.runTechCatalogImportJob = async () => { ctrl.sendLogQuery(jobName + ' - ' + status, jobUser, jobName, res); // run daily import - techCatImport.runDailyTechCatalogImport({ body : { refreshtoken : process.env.FLEXERA_REFRESH_TOKEN, requester : jobUser } }, res) - .then((response) => { - status = `finished successfully: \n` + response; - console.log(jobName + ' - ' + status); - }) - .catch((error) => { - status = `error occurred while running: \n` + error; - console.log(jobName + ' - ' + status); - }); + techCatImport.runDailyTechCatalogImport({ body: { refreshtoken: process.env.FLEXERA_REFRESH_TOKEN, requester: jobUser } }, res) + .then((response) => { + status = `finished successfully: \n` + response; + console.log(jobName + ' - ' + status); + }) + .catch((error) => { + status = `error occurred while running: \n` + error; + console.log(jobName + ' - ' + status); + }); } catch (error) { status = `error occurred starting: \n` + error; @@ -72,30 +85,6 @@ exports.runTechCatalogImportJob = async () => { }; // ------------------------------------------------------------------------------------------------- -// CRON JOB: Touch point Daily Import (runs daily at 5:00 AM) -exports.runTouchpointImportJob = async () => { - - const jobName = 'CRON JOB: Touchpoint Daily Import'; - let status = 'executed'; - - console.log(jobName + ' - ' + status); - - try { - - let res = {}; - - // log execution of job - ctrl.sendLogQuery(jobName + ' - ' + status, jobUser, jobName, res); - - // run daily import - touchpointImport.importWebsiteData(); - } catch (error) { - status = `error occurred starting: \n` + error; - console.log(jobName + ' - ' + status); - } - -}; - /* * Function to get FISMA info from ServiceNow API * everyday at 20:00 Eastern Time @@ -180,4 +169,5 @@ const putData = async data => { console.log(jobName + ' - ' + status); } -});*/ \ No newline at end of file +});*/ + diff --git a/api/controllers/records.controller.js b/api/controllers/records.controller.js index 48bb62a3..f18b1ced 100644 --- a/api/controllers/records.controller.js +++ b/api/controllers/records.controller.js @@ -87,27 +87,29 @@ exports.updateSystems = (req, res) => { }; // This function is called by the api to update all the records using a Google Sheet -exports.refreshAllSystems = (req, res) => { - ctrl.getApiToken (req, res) - .then((response) => { +exports.refreshAllSystems = async (req, res) => { + try { + const response = await ctrl.getApiToken(req, res); console.log('*** API Security Testing - getApiToken response: ', response); //DEBUGGING - if (response === 1) { console.log('*** API Security Testing - API Auth Validation: PASSED'); //DEBUGGING - var data = req.body; - - console.log("refreshAllSystems") - - // if (req.headers.authorization) { - // 'refresh' is used to set the callback_method = "refresh" - ctrl.googleMain(res, 'refresh', SHEET_ID, RANGE, req.headers.requester); - } else { - console.log('*** API Security Testing - API Auth Validation: FAILED'); //DEBUGGING - res.status(502).json({ - message: "No authorization token present." + + console.log("refreshAllSystems") + // 'refresh' is used to set the callback_method = "refresh" + await ctrl.googleMain(res, 'refresh', SHEET_ID, RANGE, req.headers.requester); + } else { + console.log('*** API Security Testing - API Auth Validation: FAILED'); //DEBUGGING + res.status(502).json({ + message: "No authorization token present." }); } - }); + } catch (error) { + msgLog('*** API Security Testing - API Auth Validation: FAILED', null); //DEBUGGING + msgLog(error.stack, null); //DEBUGGING + res.status(502).json({ + message: "No authorization token present." + }); + } }; // this function is called by the api to log an event to the database diff --git a/api/controllers/touchpoint-import.controller.js b/api/controllers/touchpoint-import.controller.js deleted file mode 100644 index c5fb314d..00000000 --- a/api/controllers/touchpoint-import.controller.js +++ /dev/null @@ -1,175 +0,0 @@ -const { equal } = require('assert'); -const { json } = require('body-parser'); -var dotenv = require('dotenv').config(); // .env Credentials - -const https = require('https'), - fs = require("fs"), - path = require("path"), - queryPath = "../queries/", - mappingsPath = "../json-mappings/", - jsonTransformEngine = require("../util/json-transform-engine.js"), - isEqual = require('lodash.isequal'), - jsonDiff = require('json-diff'); - -const sql_promise = require("../db.js").connection_promise; - -const apiKey = process.env.TOUCHPOINT_API_KEY; -const touchpointHost = "api.gsa.gov"; -const touchpointUrlPath = `/analytics/touchpoints/v1/websites.json?all=1&API_KEY=${apiKey}`; - -const insert_params = ["analytics_url", "authentication_tool", "cms_platform", "contact_email", "dap_gtm_code", "digital_brand_category", "domain", "feedback_tool", "has_authenticated_experience", "has_dap", "has_search", "hosting_platform", "https", "id", "mobile_friendly", "notes", "office", "production_status", "redirects_to", "repository_url", "required_by_law_or_policy", "site_owner_email", "sitemap_url", "status_code", "sub_office", "type_of_site", "uses_feedback", "uses_tracking_cookies", "uswds_version", "created_at", "updated_at", "target_decommission_date"]; - -const update_params = ["analytics_url", "authentication_tool", "cms_platform", "contact_email", "dap_gtm_code", "digital_brand_category", "domain", "feedback_tool", "has_authenticated_experience", "has_dap", "has_search", "hosting_platform", "https", "id", "mobile_friendly", "notes", "office", "production_status", "redirects_to", "repository_url", "required_by_law_or_policy", "site_owner_email", "sitemap_url", "status_code", "sub_office", "type_of_site", "uses_feedback", "uses_tracking_cookies", "uswds_version", "created_at", "updated_at", "target_decommission_date", "id"]; - - -const doRequest = (options) => { - return new Promise((resolve, reject) => { - let req = https.request(options); - - req.on('response', res => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - - res.on('end', () => { - const jsonData = JSON.parse(data); - resolve(jsonData); - }); - }); - - req.on('error', err => { - reject(err); - }); - - req.end(); - }); -} - -const getData = async () => { - const options = { - host: touchpointHost, - path: touchpointUrlPath, - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }; - - return await doRequest(options); -} - -const runQuery = async (query, values) => { - [rows, fields] = await sql_promise.query(query, values); - return rows; -}; - -const getDbData = async () => { - const query = fs.readFileSync(path.join(__dirname, queryPath, "GET/get_touchpoint_websites.sql")).toString(); - return await runQuery(query); -}; - -const insertDbData = async (rowData) => { - const query = fs.readFileSync(path.join(__dirname, queryPath, "CREATE/insert_websites_from_touchpoint.sql")).toString(); - const values = insert_params.map(paramName => rowData[paramName]); - const result = await runQuery(query, values); - //console.log(`Insert::: id: ${rowData.id}, row: ${JSON.stringify(result)}`); -}; - -const updateDbData = async (rowData) => { - const query = fs.readFileSync(path.join(__dirname, queryPath, "UPDATE/update_websites_from_touchpoint.sql")).toString(); - const values = update_params.map(paramName => rowData[paramName]); - const result = await runQuery(query, values); - //console.log(`Update::: id: ${rowData.id}, result: ${JSON.stringify(result)}`); -}; - -const removeDbData = async (rowIds) => { - const query = fs.readFileSync(path.join(__dirname, queryPath, "REMOVE/remove_websites_by_ids.sql")).toString(); - const result = await runQuery(query, [rowIds]); - //console.log(`Remove rows::: result: ${JSON.stringify(result)}`); -}; - -const analyzeData = async (dataItems) => { - // get touch point data for comparison - const mappingsJson = JSON.parse(fs.readFileSync(path.join(__dirname, mappingsPath, "touchpoint-to-website.json"))); - const touchpointDbRows = (await Promise.all(dataItems - .filter(dataItem => dataItem["attributes"]["organization_id"] === 1) - .map(async (dataItem) => await jsonTransformEngine.transform(dataItem, mappingsJson)))) - .sort((a, b) => a.id - b.id); - const touchpointRowMap = Object.assign({}, ...touchpointDbRows.map(row => { return { [row.id]: row } })); - - const dbRows = (await getDbData()).sort((a, b) => a.id - b.id); - const dbRowMap = Object.assign({}, ...dbRows.map(row => { return { [row.id]: row } })); - - console.log(`Total touchpoint count: ${touchpointDbRows.length}`); - console.log(`Total db count: ${dbRows.length}`); - - updateIds = Object.keys(dbRowMap).filter(dbId => (dbId in touchpointRowMap) && !isEqual(dbRowMap[dbId], touchpointRowMap[dbId])); - console.log("Update count: " + updateIds.length); - //print data differences - updateIds.forEach(id => console.log(`Update::: id: ${id}, diff: ${jsonDiff.diffString(dbRowMap[id], touchpointRowMap[id])}`)); - console.log(`Update Count: ${updateIds.length}`); - //console.log(`Update Ids: ${JSON.stringify(updateIds)}`); - - - newIds = Object.keys(touchpointRowMap).filter(dbId => !(dbId in dbRowMap)) - console.log(`New Count: ${newIds.length}`); - //console.log(`New Ids: ${JSON.stringify(newIds)}`); - - removeIds = Object.keys(dbRowMap).filter(dbId => !(dbId in touchpointRowMap)) - console.log(`Remove Count: ${removeIds.length}`); - //console.log(`Remove Ids: ${JSON.stringify(removeIds)}`); - - return [newIds, updateIds, removeIds] -}; - -const transformTouchpointData = async (tpDataItems, mappingFileName, filterIds = []) => { - // get complete touch point data json for import - const mappingsCompleteJson = JSON.parse(fs.readFileSync(path.join(__dirname, mappingsPath, mappingFileName))); - return await Promise.all(tpDataItems - .filter(dataItem => dataItem["attributes"]["organization_id"] === 1 && - (filterIds.length === 0 || filterIds.indexOf(dataItem.id) !== -1)) - .map(async (dataItem) => await jsonTransformEngine.transform(dataItem, mappingsCompleteJson))); -} - -const createData = async (tpDataItems, rowIds) => { - if (!rowIds || rowIds.length === 0) { - console.log(`${new Date()}: no new items.`) - return; - } - const tpDataRows = await transformTouchpointData(tpDataItems, "touchpoint-to-website-complete.json", rowIds); - console.log(`new row count ${tpDataRows.length}`); - await Promise.all(tpDataRows.map(async (rowData) => await insertDbData(rowData))); - console.log(`${new Date()}: Insertion is complete.`); -}; - -const updateData = async (tpDataItems, rowIds) => { - if (!rowIds || rowIds.length === 0) { - console.log(`${new Date()}: no update items.`) - return; - } - const tpDataRows = await transformTouchpointData(tpDataItems, "touchpoint-to-website-complete.json", rowIds); - console.log(`${new Date()}: update row count ${tpDataRows.length}`); - await Promise.all(tpDataRows.map(async (rowData) => await updateDbData(rowData))); - console.log(`${new Date()}: Update is complete.`); -}; - -const removeData = async (rowIds) => { - if (!rowIds || rowIds.length === 0) { - console.log(`${new Date()}: no remove items.`) - return; - } - console.log(`${new Date()}: delete row count ${rowIds.length}`); - await removeDbData(rowIds); - console.log(`${new Date()}: Delete is complete.`); -}; - -exports.importWebsiteData = async () => { - const tpDataObj = await getData(); - const tpDataItems = tpDataObj["data"]; - - const [newIds, updateIds, removeIds] = await analyzeData(tpDataItems); - await createData(tpDataItems, newIds); - await updateData(tpDataItems, updateIds); - await removeData(removeIds); -} diff --git a/api/cron-jobs/cron-job-db-util.service.js b/api/cron-jobs/cron-job-db-util.service.js new file mode 100644 index 00000000..1f1c6961 --- /dev/null +++ b/api/cron-jobs/cron-job-db-util.service.js @@ -0,0 +1,49 @@ +const { prepareQuery, runQuery } = require("../util/db-query-service"); + +const INSERT_PARAMS = ["jobType", "startTime", "jobLogs", "jobStatus"]; +const UPDATE_PARAMS = ["jobStatus", "endTime", "jobLogs", "jobId"]; + +/** + * Retrieves any pending job of the specified type. + * + * @param {string} jobType - The type of job to look for. + * @returns {Promise} A promise that resolves to the jobId of the pending job or null if no job is found. + */ +const getAnyPendingJob = async (jobType) => { + const query = await prepareQuery("GET/get_any_pending_job_by_type.sql"); + const result = await runQuery(query, [jobType]); + return result && result.length > 0 ? result[0].jobId : null; +}; + +/** + * Inserts a new job record into the database. + * + * @param {Object} rowData - The data for the new job. + * @returns {Promise} A promise that resolves to the ID of the inserted job. + */ +const insertDbData = async (rowData) => { + const query = await prepareQuery("CREATE/insert_cron_job.sql"); + const values = INSERT_PARAMS.map(paramName => rowData[paramName]); + const result = await runQuery(query, values); + return result.insertId; +}; + +/** + * Updates an existing job record in the database. + * + * @param {Object} rowData - The data to update the job with. + * @returns {Promise} A promise that resolves to the result of the update operation. + */ +const updateDbData = async (rowData) => { + const query = await prepareQuery("UPDATE/update_cron_job_status.sql"); + const values = UPDATE_PARAMS.map(paramName => rowData[paramName]); + const result = await runQuery(query, values); + return result; +}; + +module.exports = { + runQuery, + getAnyPendingJob, + insertDbData, + updateDbData, +}; \ No newline at end of file diff --git a/api/cron-jobs/job-logger.js b/api/cron-jobs/job-logger.js new file mode 100644 index 00000000..7dc8df17 --- /dev/null +++ b/api/cron-jobs/job-logger.js @@ -0,0 +1,34 @@ +/** + * JobLogger class for managing log messages. + */ +class JobLogger { + /** + * Creates a new JobLogger instance. + */ + constructor() { + this.logs = ''; // Initialize an empty string to store logs + } + + /** + * Appends a new log message to the logs. + * + * @param {string} message - The log message to be added. + */ + log(message) { + this.logs += `${message}\n`; // Append log to the string with a newline + } + + /** + * Retrieves all the log messages. + * + * @returns {string} All the log messages concatenated into a single string. + */ + getLogs() { + return this.logs; // Return the concatenated log messages + } + } + + module.exports = { + JobLogger, + }; + \ No newline at end of file diff --git a/api/cron-jobs/poc-job-handler.js b/api/cron-jobs/poc-job-handler.js new file mode 100644 index 00000000..75a9c6c8 --- /dev/null +++ b/api/cron-jobs/poc-job-handler.js @@ -0,0 +1,131 @@ +const { JobLogger } = require('../cron-jobs/job-logger.js'); +const JobStatus = require('../enums/job-status.js'); +const cronJobDbUtilService = require("../cron-jobs/cron-job-db-util.service.js"); +const { formatDateTime } = require('../util/date-time-format-service.js'); +const { parseCSV } = require("../util/csv-parse-service.js"); +const { runQuery, getConnection, relaseConnection } = require("../util/db-query-service.js"); + +const jobType = "POC-JOB"; +const jobName = `CRON JOB: POC Update Job`; + +/** + * Runs the POC job. Updates the POC data. Logs execution details and job status into the database. + */ +const runPocJob = async () => { + const jobLogger = new JobLogger(); + let jobId; + let dbConn; + + try { + jobLogger.log(`${jobName} - Execution start`); + + // Check for any pending job + const pendingJobId = await cronJobDbUtilService.getAnyPendingJob(jobType); + if (pendingJobId) { + jobLogger.log(`Active Job '${pendingJobId}' is Running. Aborting the job now.`); + jobId = await cronJobDbUtilService.insertDbData({ jobType, startTime: formatDateTime(new Date()), jobLogs: jobLogger.getLogs(), jobStatus: JobStatus.CANCELLED }); + return; + } + + // Insert new job record + jobId = await cronJobDbUtilService.insertDbData({ jobType, startTime: formatDateTime(new Date()), jobLogs: '', jobStatus: JobStatus.PENDING }); + console.log(`Cron job id: ${jobId} - start`); + + // TODO: Add code to get data using Active Directory using activedirectory2 lib + + // Parse the CSV file & skip the first row + const pocCsv = await parseCSV("scripts/pocs/GSA_Pocs.csv", false, 1); + + // Clear tmp_obj_ldap_poc table + const clearQuery = "DELETE FROM tmp_obj_ldap_poc"; + await runQuery(clearQuery, []); + jobLogger.log("Clear tmp_obj_ldap_poc records."); + + // Insert parsed data into tmp_obj_ldap_poc + const insertQuery = "REPLACE INTO tmp_obj_ldap_poc (SamAccountName, FirstName, LastName, Email, Phone, OrgCode, Position, EmployeeType, Enabled) VALUES ?"; + await runQuery(insertQuery, [pocCsv]); + jobLogger.log('Insert into tmp_obj_ldap_poc.'); + + // Upsert data into obj_ldap_poc + const upsertQuery = ` + INSERT INTO obj_ldap_poc (SamAccountName, FirstName, LastName, Email, Phone, OrgCode, Position, EmployeeType, Enabled) + SELECT t.SamAccountName, t.FirstName, t.LastName, t.Email, t.Phone, t.OrgCode, t.Position, t.EmployeeType, t.Enabled + FROM tmp_obj_ldap_poc t + ON DUPLICATE KEY UPDATE + FirstName = VALUES(FirstName), LastName = VALUES(LastName), + Email = VALUES(Email), Phone = VALUES(Phone), OrgCode = VALUES(OrgCode), + Position = VALUES(Position), EmployeeType = VALUES(EmployeeType), Enabled = VALUES(Enabled) + `; + await runQuery(upsertQuery, []); + jobLogger.log('Insert into/update obj_ldap_poc.'); + + // Update obj_ldap_poc to disable records + const updateQuery = ` + UPDATE obj_ldap_poc poc + SET Enabled = 'FALSE' + WHERE poc.SamAccountName NOT IN (SELECT SamAccountName FROM tmp_obj_ldap_poc) + `; + await runQuery(updateQuery, []); + jobLogger.log('Update obj_ldap_poc to disable records.'); + + // Delete disabled records from obj_ldap_poc + const deleteQuery = ` + DELETE FROM obj_ldap_poc + WHERE Enabled = 'FALSE' + AND SamAccountName NOT IN (SELECT obj_ldap_SamAccountName FROM zk_technology_poc) + `; + await runQuery(deleteQuery, []); + jobLogger.log('Delete obj_ldap_poc disabled records.'); + + // Update End of Life records and add a group account + const updateEndOfLifeQuery = ` + UPDATE obj_ldap_poc poc + SET EmployeeType = 'Separated' + WHERE poc.SamAccountName NOT IN (SELECT SamAccountName FROM tmp_obj_ldap_poc) AND Enabled = 'FALSE' AND poc.EmployeeType = ''; + + INSERT INTO gear_schema.obj_ldap_poc (SamAccountName, FirstName, LastName, Email, EmployeeType, Enabled, RISSO) + VALUES ('AssistTechTeam', 'Assist', 'Tech Team', 'assisttechteam@gsa.gov', 'Group', 'True', '24') + ON DUPLICATE KEY UPDATE SamAccountName = 'AssistTechTeam'; + `; + await runQuery(updateEndOfLifeQuery, []); + jobLogger.log('Update obj_ldap_poc to separate records and add group account.'); + + await postprocesJobExecution(jobId, jobLogger, JobStatus.SUCCESS); + } catch (error) { + // Log any errors + const status = `Error occurred while running: \n${error}`; + if (jobId) { + jobLogger.log(`${jobName} - ${status}`); + jobLogger.log(error.stack); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } else { + jobLogger.log(error); + jobLogger.log(error.stack); + console.log(jobLogger.getLogs()); + } + } finally { + console.log(`Cron job id: ${jobId} - end`); + } +}; + +/** + * Finalizes the job execution by updating the job status and logs in the database. + * + * @param {number} jobId - The ID of the job to be updated. + * @param {JobLogger} jobLogger - The JobLogger instance containing the job logs. + * @param {string} jobStatus - The status to update the job with (e.g., SUCCESS, FAILURE). + * @returns {Promise} A promise that resolves when the job update is complete. + */ +const postprocesJobExecution = async (jobId, jobLogger, jobStatus) => { + jobLogger.log(`Cron job id: ${jobId} - end`); + await cronJobDbUtilService.updateDbData({ + jobStatus: jobStatus, + endTime: formatDateTime(new Date()), + jobLogs: jobLogger.getLogs(), + jobId: jobId + }); +}; + +module.exports = { + runPocJob, +}; \ No newline at end of file diff --git a/api/cron-jobs/tpi-job-handler.js b/api/cron-jobs/tpi-job-handler.js new file mode 100644 index 00000000..19d9c2ad --- /dev/null +++ b/api/cron-jobs/tpi-job-handler.js @@ -0,0 +1,189 @@ +const isEqual = require('lodash.isequal'); +const jsonDiff = require('json-diff'); + +const { getFilePath, parseFile, } = require("../util/io-service.js"); +const { JobLogger } = require('../cron-jobs/job-logger.js'); +const JobStatus = require('../enums/job-status.js'); +const cronJobDbUtilService = require("../cron-jobs/cron-job-db-util.service.js"); +const { formatDateTime } = require('../util/date-time-format-service.js'); +const jsonTransformEngine = require("../util/json-transform-engine.js"); +const { getJsonData } = require("../util/https-client-service.js"); +const { prepareQuery, runQuery } = require("../util/db-query-service.js"); + +const jobType = "TPI-JOB"; +const jobName = `CRON JOB: Touchpoint Daily Import`; +const mappingsPath = "../json-mappings/"; + +const apiKey = process.env.TOUCHPOINT_API_KEY; +const touchpointHost = "api.gsa.gov"; +const touchpointUrlPath = `/analytics/touchpoints/v1/websites.json?all=1&API_KEY=${apiKey}`; +const insert_params = ["analytics_url", "authentication_tool", "cms_platform", "contact_email", "dap_gtm_code", "digital_brand_category", "domain", "feedback_tool", "has_authenticated_experience", "has_dap", "has_search", "hosting_platform", "https", "id", "mobile_friendly", "notes", "office", "production_status", "redirects_to", "repository_url", "required_by_law_or_policy", "site_owner_email", "sitemap_url", "status_code", "sub_office", "type_of_site", "uses_feedback", "uses_tracking_cookies", "uswds_version", "created_at", "updated_at", "target_decommission_date"]; +const update_params = ["analytics_url", "authentication_tool", "cms_platform", "contact_email", "dap_gtm_code", "digital_brand_category", "domain", "feedback_tool", "has_authenticated_experience", "has_dap", "has_search", "hosting_platform", "https", "id", "mobile_friendly", "notes", "office", "production_status", "redirects_to", "repository_url", "required_by_law_or_policy", "site_owner_email", "sitemap_url", "status_code", "sub_office", "type_of_site", "uses_feedback", "uses_tracking_cookies", "uswds_version", "created_at", "updated_at", "target_decommission_date", "id"]; + + +const getDbData = async () => { + const query = await prepareQuery("GET/get_touchpoint_websites.sql"); + return await runQuery(query); +}; + +const insertDbData = async (rowData) => { + const query = await prepareQuery("CREATE/insert_websites_from_touchpoint.sql"); + const values = insert_params.map(paramName => rowData[paramName]); + await runQuery(query, values); +}; + +const updateDbData = async (rowData) => { + const query = await prepareQuery("UPDATE/update_websites_from_touchpoint.sql"); + const values = update_params.map(paramName => rowData[paramName]); + await runQuery(query, values); +}; + +const removeDbData = async (rowIds) => { + const query = await prepareQuery("REMOVE/remove_websites_by_ids.sql"); + await runQuery(query, [rowIds]); +}; + +const analyzeData = async (dataItems, jobLogger) => { + // get touch point data for comparison + const mappingsJson = JSON.parse(await parseFile(getFilePath(mappingsPath, "touchpoint-to-website.json"))); + const touchpointDbRows = (await Promise.all(dataItems + .filter(dataItem => dataItem["attributes"]["organization_id"] === 1) + .map(async (dataItem) => await jsonTransformEngine.transform(dataItem, mappingsJson)))) + .sort((a, b) => a.id - b.id); + + const touchpointRowMap = Object.assign({}, ...touchpointDbRows.map(row => { return { [row.id]: row } })); + const dbRows = (await getDbData()).sort((a, b) => a.id - b.id); + const dbRowMap = Object.assign({}, ...dbRows.map(row => { return { [row.id]: row } })); + + jobLogger.log(`Total touchpoint count: ${touchpointDbRows.length}`); + jobLogger.log(`Total db count: ${dbRows.length}`); + + updateIds = Object.keys(dbRowMap).filter(dbId => (dbId in touchpointRowMap) && !isEqual(dbRowMap[dbId], touchpointRowMap[dbId])); + jobLogger.log("Update count: " + updateIds.length); + //print data differences + updateIds.forEach(id => jobLogger.log(`Update::: id: ${id}, diff: ${jsonDiff.diffString(dbRowMap[id], touchpointRowMap[id])}`)); + jobLogger.log(`Update Count: ${updateIds.length}`); + + + newIds = Object.keys(touchpointRowMap).filter(dbId => !(dbId in dbRowMap)) + jobLogger.log(`New Count: ${newIds.length}`); + + removeIds = Object.keys(dbRowMap).filter(dbId => !(dbId in touchpointRowMap)) + jobLogger.log(`Remove Count: ${removeIds.length}`); + + return [newIds, updateIds, removeIds] +}; + +const transformTouchpointData = async (tpDataItems, mappingFileName, filterIds = []) => { + // get complete touch point data json for import + const mappingsCompleteJson = JSON.parse(await parseFile(getFilePath(mappingsPath, mappingFileName))); + return await Promise.all(tpDataItems + .filter(dataItem => dataItem["attributes"]["organization_id"] === 1 && + (filterIds.length === 0 || filterIds.indexOf(dataItem.id) !== -1)) + .map(async (dataItem) => await jsonTransformEngine.transform(dataItem, mappingsCompleteJson))); +} + +const createData = async (tpDataItems, rowIds, jobLogger) => { + if (!rowIds || rowIds.length === 0) { + jobLogger.log(`${new Date()}: no new items.`) + return; + } + const tpDataRows = await transformTouchpointData(tpDataItems, "touchpoint-to-website-complete.json", rowIds); + jobLogger.log(`new row count ${tpDataRows.length}`); + await Promise.all(tpDataRows.map(async (rowData) => await insertDbData(rowData))); + jobLogger.log(`${new Date()}: Insertion is complete.`); +}; + +const updateData = async (tpDataItems, rowIds, jobLogger) => { + if (!rowIds || rowIds.length === 0) { + jobLogger.log(`${new Date()}: no update items.`) + return; + } + const tpDataRows = await transformTouchpointData(tpDataItems, "touchpoint-to-website-complete.json", rowIds); + jobLogger.log(`${new Date()}: update row count ${tpDataRows.length}`); + await Promise.all(tpDataRows.map(async (rowData) => await updateDbData(rowData))); + jobLogger.log(`${new Date()}: Update is complete.`); +}; + +const removeData = async (rowIds, jobLogger) => { + if (!rowIds || rowIds.length === 0) { + jobLogger.log(`${new Date()}: no remove items.`) + return; + } + jobLogger.log(`${new Date()}: delete row count ${rowIds.length}`); + await removeDbData(rowIds); + jobLogger.log(`${new Date()}: Delete is complete.`); +}; + +const importWebsiteData = async (jobLogger) => { + const tpDataObj = await getJsonData(touchpointHost, touchpointUrlPath); + const tpDataItems = tpDataObj["data"]; + + const [newIds, updateIds, removeIds] = await analyzeData(tpDataItems, jobLogger); + await createData(tpDataItems, newIds, jobLogger); + await updateData(tpDataItems, updateIds, jobLogger); + await removeData(removeIds, jobLogger); +} + + +/** + * Runs the Touchpoint Daily Import job. Updates the Websites data. Logs execution details and job status into the database. + */ +const runTouchpointImportJob = async () => { + const jobLogger = new JobLogger(); + let jobId; + + try { + jobLogger.log(`${jobName} - Execution start`); + + // Check for any pending job + const pendingJobId = await cronJobDbUtilService.getAnyPendingJob(jobType); + if (pendingJobId) { + jobLogger.log(`Active Job '${pendingJobId}' is Running. Aborting the job now.`); + await cronJobDbUtilService.insertDbData({ jobType, startTime: formatDateTime(new Date()), jobLogs: jobLogger.getLogs(), jobStatus: JobStatus.CANCELLED }); + return; + } + + // Insert new job record + jobId = await cronJobDbUtilService.insertDbData({ jobType, startTime: formatDateTime(new Date()), jobLogs: '', jobStatus: JobStatus.PENDING }); + jobLogger.log(`Cron job id: ${jobId} - start`); + + await importWebsiteData(jobLogger); + await postprocesJobExecution(jobId, jobLogger, JobStatus.SUCCESS); + } catch (error) { + // Log any errors + const status = `Error occurred while running: \n${error}`; + if (jobId) { + jobLogger.log(`${jobName} - ${status}`); + jobLogger.log(error.stack); + await postprocesJobExecution(jobId, jobLogger, JobStatus.FAILURE); + } else { + jobLogger.log(error); + jobLogger.log(jobLogger.getLogs()); + } + } finally { + console.log(`Cron job id: ${jobId} - end`); + } +}; + +/** + * Finalizes the job execution by updating the job status and logs in the database. + * + * @param {number} jobId - The ID of the job to be updated. + * @param {JobLogger} jobLogger - The JobLogger instance containing the job logs. + * @param {string} jobStatus - The status to update the job with (e.g., SUCCESS, FAILURE). + * @returns {Promise} A promise that resolves when the job update is complete. + */ +const postprocesJobExecution = async (jobId, jobLogger, jobStatus) => { + jobLogger.log(`Cron job id: ${jobId} - end`); + await cronJobDbUtilService.updateDbData({ + jobStatus: jobStatus, + endTime: formatDateTime(new Date()), + jobLogs: jobLogger.getLogs(), + jobId: jobId + }); +}; + +module.exports = { + runTouchpointImportJob, +}; \ No newline at end of file diff --git a/api/db.js b/api/db.js index 32792eee..76e585a0 100644 --- a/api/db.js +++ b/api/db.js @@ -1,6 +1,7 @@ var dotenv = require('dotenv').config(); // .env Credentials const fs = require('fs'); const mysql = require('mysql2'); +const asyncMysql = require('mysql2/promise'); // Connection Credentials dbCredentials = { @@ -31,13 +32,13 @@ dbCredentials = { // Create DB Connection const pool = mysql.createPool(dbCredentials) -const promisePool = pool.promise(); +const promisePool = asyncMysql.createPool(dbCredentials); // const pool_cowboy = mysql.createPool(dbCredentials_cowboy) module.exports = { dbCredentials: dbCredentials, connection: pool, - connection_promise: promisePool + promisePool: promisePool // connection_cowboy: pool_cowboy }; \ No newline at end of file diff --git a/api/enums/job-status.js b/api/enums/job-status.js new file mode 100644 index 00000000..aa2ae23b --- /dev/null +++ b/api/enums/job-status.js @@ -0,0 +1,7 @@ +const JObStaus = Object.freeze({ + SUCCESS: 1, + FAILURE: 2, + PENDING: 3, + CANCELLED: 4 +}); +module.exports = JObStaus; \ No newline at end of file diff --git a/api/queries/CREATE/insert_cron_job.sql b/api/queries/CREATE/insert_cron_job.sql new file mode 100644 index 00000000..93cb5b46 --- /dev/null +++ b/api/queries/CREATE/insert_cron_job.sql @@ -0,0 +1 @@ +INSERT INTO cron_job (job_type, start_time, job_logs, job_status) values(?, ?, ?, ?) \ No newline at end of file diff --git a/api/queries/GET/get_any_pending_job_by_type.sql b/api/queries/GET/get_any_pending_job_by_type.sql new file mode 100644 index 00000000..69fdd9d1 --- /dev/null +++ b/api/queries/GET/get_any_pending_job_by_type.sql @@ -0,0 +1,9 @@ +SELECT + job_id AS jobId, + job_type AS jobType, + start_time AS startTime, + end_time AS endTime, + job_status AS jobStatus +FROM cron_job +WHERE job_type = ? and job_status=3 +ORDER BY start_time DESC limit 1 \ No newline at end of file diff --git a/api/queries/UPDATE/update_cron_job_status.sql b/api/queries/UPDATE/update_cron_job_status.sql new file mode 100644 index 00000000..05b5eab2 --- /dev/null +++ b/api/queries/UPDATE/update_cron_job_status.sql @@ -0,0 +1,5 @@ +UPDATE cron_job SET +job_status = ?, +end_time= ?, +job_logs = ? +WHERE job_id = ? \ No newline at end of file diff --git a/api/util/csv-parse-service.js b/api/util/csv-parse-service.js new file mode 100644 index 00000000..85c47832 --- /dev/null +++ b/api/util/csv-parse-service.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const { parse } = require('fast-csv'); + +/** + * Parses a CSV file and returns the data as an array of data. + * + * @param {string} filePath - The path to the CSV file. + * @param {boolean|Array} [headers=false] - The headers option for parsing the CSV. If true, the first row of the CSV is treated as headers. + * @param {number} [skipLines=0] - The number of lines to skip before starting to parse. + * @returns {Promise>} A promise that resolves to an array of objects representing the parsed CSV data. + */ +const parseCSV = async (filePath, headers = false, skipLines = 0) => { + const results = []; + + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(filePath) + .on('error', error => reject(error)); + + stream.pipe(parse({ headers, skipLines })) + .on('data', row => results.push(row)) + .on('end', () => resolve(results)) + .on('error', error => reject(error)); + }); +} + +module.exports = { + parseCSV, +} diff --git a/api/util/date-time-format-service.js b/api/util/date-time-format-service.js new file mode 100644 index 00000000..6e8f51d6 --- /dev/null +++ b/api/util/date-time-format-service.js @@ -0,0 +1,36 @@ + +/** + * Formats the date and time for logging. + * + * @param {Date|string} dateObj - The date object or date string to be formatted. + * @returns {string|null} The formatted date string in 'yyyy-mm-dd hh:mm:ss.mmm' format or null if invalid. + */ +const formatDateTime = (dateObj) => { + if (dateObj === null || dateObj === '') { + return null; + } + + let formattedDate = new Date(dateObj); + if (isNaN(formattedDate)) { + return null; + } + + // Get the individual date components + const year = formattedDate.getFullYear(); + const month = String(formattedDate.getMonth() + 1).padStart(2, '0'); + const day = String(formattedDate.getDate()).padStart(2, '0'); + + // Get the individual time components + const hours = String(formattedDate.getHours()).padStart(2, '0'); + const minutes = String(formattedDate.getMinutes()).padStart(2, '0'); + const seconds = String(formattedDate.getSeconds()).padStart(2, '0'); + const milliseconds = String(formattedDate.getMilliseconds()).padStart(3, '0'); + + // Combine the components into the desired format + formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; + return formattedDate; + }; + +module.exports = { + formatDateTime, +}; \ No newline at end of file diff --git a/api/util/db-query-service.js b/api/util/db-query-service.js new file mode 100644 index 00000000..5e4caf18 --- /dev/null +++ b/api/util/db-query-service.js @@ -0,0 +1,53 @@ +const { getFilePath, parseFile } = require("./io-service.js"); +const { promisePool: connPromisePool } = require("../db.js"); +const queryPath = '../queries/'; + +/** + * Prepares an SQL query by reading its content from a file. + * + * @param {string} queryFilePath - The path to the query file. + * @returns {Promise} A promise that resolves to the query string. + */ +const prepareQuery = async (queryFilePath) => { + return await parseFile(getFilePath(queryPath, queryFilePath)); +}; + +/** + * Retries the provided asynchronous function in case of a deadlock error. + * + * @param {Function} func - The asynchronous function to be retried. + * @param {number} [count=0] - The current retry count. + * @param {number} [limit=5] - The maximum number of retry attempts. + * @param {number} [wait=3000] - The initial wait time in milliseconds before retrying. + * @returns {Promise} A promise that resolves to the result of the function if successful. + * @throws {Error} Throws an error if the retry limit is reached or a non-deadlock error occurs. + */ +const retry = async (func, count = 0, limit = 5, wait = 3000) => { + try { + return await func(); + } catch (err) { + if (err.code === 'ER_LOCK_DEADLOCK' && count < limit) { + await new Promise(resolve => setTimeout(resolve, wait)); + return retry(func, count + 1, limit, wait * 2); + } else { + throw err; + } + } +}; + +/** + * Executes a query on the database with the given values. + * + * @param {string} query - The SQL query to be executed. + * @param {Array} values - The values to be used in the query. + * @returns {Promise} A promise that resolves to the rows returned by the query. + */ +const runQuery = async (query, values) => { + const [rows] = await retry(async() => await connPromisePool.query(query, values)); + return rows; +}; + +module.exports = { + prepareQuery, + runQuery, +}; \ No newline at end of file diff --git a/api/util/https-client-service.js b/api/util/https-client-service.js new file mode 100644 index 00000000..cfbae509 --- /dev/null +++ b/api/util/https-client-service.js @@ -0,0 +1,44 @@ +const https = require('https'); + +/** + * Performs an HTTPS request and returns the response data as JSON. + * + * @param {Object} options - The options for the HTTPS request. + * @returns {Promise} A promise that resolves to the response data as JSON. + */ +const doRequest = (options) => { + return new Promise((resolve, reject) => { + const req = https.request(options, res => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(JSON.parse(data))); + }); + + req.on('error', reject); + req.end(); + }); +}; + +/** + * Fetches JSON data from the specified host and path. + * + * @param {string} host - The host name. + * @param {string} path - The path on the host. + * @returns {Promise} A promise that resolves to the JSON data. + */ +const getJsonData = async (host, path) => { + const options = { + host, + path, + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }; + + return await doRequest(options); +}; + +module.exports = { + getJsonData, +} diff --git a/api/util/io-service.js b/api/util/io-service.js new file mode 100644 index 00000000..5242b72e --- /dev/null +++ b/api/util/io-service.js @@ -0,0 +1,24 @@ +const { promises: fs } = require('fs'); +const path = require('path'); + +/** + * Gets the full file path by combining base path, file type path, and file path. + * + * @param {string} fileTypePath - The type path of the file. + * @param {string} filePath - The path to the file. + * @returns {string} The combined file path. + */ +const getFilePath = (fileTypePath, filePath) => path.join(__dirname, fileTypePath, filePath); + +/** + * Reads and returns the content of a file as a string. + * + * @param {string} filePath - The path to the file. + * @returns {Promise} A promise that resolves to the file content as a string. + */ +const parseFile = async (filePath) => await fs.readFile(filePath, 'utf8'); + +module.exports = { + getFilePath, + parseFile, +}; diff --git a/api/util/json-transform-engine.js b/api/util/json-transform-engine.js index 8bc96185..0f036cd8 100644 --- a/api/util/json-transform-engine.js +++ b/api/util/json-transform-engine.js @@ -1,18 +1,51 @@ -const jexl = require('jexl') +const jexl = require('jexl'); +/** + * Adds a custom transform to Jexl to convert boolean values to text. + * + * @param {boolean} val - The boolean value to convert. + * @returns {string} 'TRUE' if the value is true, 'FALSE' if the value is false, otherwise an empty string. + */ jexl.addTransform('convertToText', (val) => val === true ? "TRUE" : val === false ? "FALSE" : ""); + +/** + * Adds a custom transform to Jexl to convert a value to a number. + * + * @param {*} val - The value to convert. + * @returns {number} The converted number value. + */ jexl.addTransform('toNumber', (val) => Number(val)); + +/** + * Adds a custom transform to Jexl to convert empty values to null. + * + * @param {*} val - The value to check. + * @returns {null|string} Null if the value is empty, 'None', or only whitespace; otherwise, the original value. + */ jexl.addTransform('emptyToNull', (val) => !val || val.trim() === '' || val.trim() === 'None' ? null : val); + +/** + * Adds a custom transform to Jexl to convert null values to empty strings. + * + * @param {*} val - The value to check. + * @returns {string} An empty string if the value is null, otherwise the original value. + */ jexl.addTransform('nullToEmpty', (val) => val === null ? '' : val); +/** + * Transforms a source JSON object based on a mapping JSON object and an optional context. + * + * @param {Object} srcJson - The source JSON object to be transformed. + * @param {Object} mappingJson - The mapping JSON object that defines how to transform the source JSON. + * @param {Object} [context={}] - An optional context object to provide additional data for the transformation. + * @returns {Promise} A promise that resolves to the transformed JSON object. + */ exports.transform = async (srcJson, mappingJson, context = {}) => { - jexlContext = { ...context, ...srcJson } - - destJson = Object.assign({}, ...await Promise.all(Object.keys(mappingJson).flatMap(async (key) => { - const valExpr = mappingJson[key]; - const value = await jexl.eval(valExpr, jexlContext); - return { [key]: value }; - }))); - - return destJson; -}; \ No newline at end of file + jexlContext = { ...context, ...srcJson }; + destJson = Object.assign({}, ...await Promise.all(Object.keys(mappingJson).flatMap(async (key) => { + const valExpr = mappingJson[key]; + const value = await jexl.eval(valExpr, jexlContext); + return { [key]: value }; + }))); + return destJson; +}; diff --git a/server.js b/server.js index 1f4d19b3..84ffeda4 100644 --- a/server.js +++ b/server.js @@ -25,6 +25,9 @@ const bodyParser = require('body-parser'), ExtractJWT = passportJWT.ExtractJwt, JWTStrategy = passportJWT.Strategy; + const pocJobHandler = require('./api/cron-jobs/poc-job-handler.js'); + const tpiJobHandler = require('./api/cron-jobs/tpi-job-handler.js'); + const CryptoJS = require("crypto-js") // Proxy Settings for Google API to use @@ -429,88 +432,17 @@ const putData = async data => { // ------------------------------------------------------------------------------------------------- // Function to load POC data every Wednesday at 5:00 AM ET from the csv file in scripts/pocs -const fastcsv = require("fast-csv"); const { consoleTestResultHandler } = require('tslint/lib/test.js'); -cron.schedule(process.env.POC_CRON, () => { //PRODUCTION - let stream = fs.createReadStream("scripts/pocs/GSA_Pocs.csv"); - let pocCsv = []; - let csvStream = fastcsv - .parse() - .on("data", function (data) { - pocCsv.push(data); - }) - .on("end", function () { - // remove the first line: header - pocCsv.shift(); - - // create a new connection to the database - const db = mysql.createConnection(dbCredentials); - - db.connect(error => { - if (error) { - console.error(error); - } else { - let clearQuery = - "DELETE FROM tmp_obj_ldap_poc"; - db.query(clearQuery, (error, response) => { - console.log(error || 'Clear tmp_obj_ldap_poc records: ' + response); - }); - - let insertQuery = - "REPLACE INTO tmp_obj_ldap_poc (SamAccountName, FirstName, LastName, Email, Phone, OrgCode, Position, EmployeeType, Enabled) VALUES ?"; - db.query(insertQuery, [pocCsv], (error, response) => { - console.log(error || 'Insert into tmp_obj_ldap_poc: ' + response); - }); - - let upsertQuery = - "INSERT INTO obj_ldap_poc (SamAccountName, FirstName, LastName, Email, Phone, OrgCode, Position, EmployeeType, Enabled) " - + "SELECT t.SamAccountName, t.FirstName, t.LastName, t.Email, t.Phone, t.OrgCode, t.Position, t.EmployeeType, t.Enabled " - + "FROM tmp_obj_ldap_poc t " - + "ON DUPLICATE KEY UPDATE FirstName = VALUES(FirstName), LastName = VALUES(LastName)," - + "Email = VALUES(Email), Phone = VALUES(Phone), OrgCode = VALUES(OrgCode)," - + "Position = VALUES(Position), EmployeeType = VALUES(EmployeeType), Enabled = VALUES(Enabled) "; - db.query(upsertQuery, (error, response) => { - console.log(error || 'Insert into/update obj_ldap_poc: ' + response); - }); - - let updateQuery = - "UPDATE obj_ldap_poc poc " - + "SET Enabled = 'FALSE' " - + "WHERE poc.SamAccountName NOT IN (SELECT SamAccountName FROM tmp_obj_ldap_poc)"; - db.query(updateQuery, (error, response) => { - console.log(error || 'Update obj_ldap_poc to disable poc: ' + response); - }); - - let deleteQuery = - "DELETE FROM obj_ldap_poc " - + "WHERE Enabled = 'FALSE' " - + "AND SamAccountName NOT IN (SELECT obj_ldap_SamAccountName FROM zk_technology_poc)"; - db.query(deleteQuery, (error, response) => { - console.log(error || 'Delete obj_ldap_poc disabled records: ' + response); - }); - - let updateEndOfLifeQuery = "UPDATE obj_ldap_poc poc " - + "SET EmployeeType = 'Separated' " - + "WHERE poc.SamAccountName NOT IN (SELECT SamAccountName FROM tmp_obj_ldap_poc) AND Enabled = 'FALSE' AND poc.EmployeeType = '';" - + "INSERT INTO `gear_schema`.`obj_ldap_poc` " - + "(`SamAccountName`, `FirstName`, `LastName`, `Email`, `EmployeeType`, `Enabled`, `RISSO`) " - + "VALUES ('AssistTechTeam', 'Assist', 'Tech Team', 'assisttechteam@gsa.gov', 'Group', 'True', '24');"; //Adding group account as part of CTO team request - db.query(updateEndOfLifeQuery, (error, response) => { - console.log(error || 'Update obj_ldap_poc to separate poc: ' + JSON.stringify(response)); - }); - } - }); - }); - - stream.pipe(csvStream); +cron.schedule(process.env.POC_CRON, async () => { //PRODUCTION + await pocJobHandler.runPocJob(); }); // ------------------------------------------------------------------------------------------------- // CRON JOB: Google Sheets API - Update All Related Records (runs every weekday at 11:00 PM) cron.schedule(process.env.RECORDS_CRON, async () => { await googleApiService.saveToken().catch(console.error); - cronCtrl.runUpdateAllRelatedRecordsJob(); + await cronCtrl.runUpdateAllRelatedRecordsJob(); }); // ------------------------------------------------------------------------------------------------- @@ -527,6 +459,6 @@ cron.schedule(process.env.TECH_CATALOG_CRON2, () => { // ------------------------------------------------------------------------------------------------- // CRON JOB: Touchpoints API - Update Websites (runs every day at 11:05 PM) -cron.schedule(process.env.TOUCHPOINTS_CRON, () => { - cronCtrl.runTouchpointImportJob(); +cron.schedule(process.env.TOUCHPOINTS_CRON, async () => { + await tpiJobHandler.runTouchpointImportJob(); }); From 63a877817916ad256074687cfcb88e3780c69124 Mon Sep 17 00:00:00 2001 From: khgsa <161092171+khgsa@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:25:38 -0700 Subject: [PATCH 20/20] change to sanitize sql input --- api/controllers/base.controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/controllers/base.controller.js b/api/controllers/base.controller.js index f8df87df..18bbf61c 100644 --- a/api/controllers/base.controller.js +++ b/api/controllers/base.controller.js @@ -7,6 +7,8 @@ const sql = require("../db.js").connection, { google } = require("googleapis") fastcsv = require("fast-csv"); +const SqlString = require('sqlstring'); + // If modifying these scopes, delete token.json. const SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"]; // The file token.json stores the user's access and refresh tokens, and is @@ -97,8 +99,7 @@ function buildLogQuery(conn, event, user, msg, response) { async function buildLogQueryAsync(conn, event, user, msg) { // - var query = `insert into gear_log.event (Event, User, DTG) values ('${event}', '${user}', now());`; - console.log(query); + var query = `insert into gear_log.event (Event, User, DTG) values ('${SqlString.escape(event)}', '${SqlString.escape(user)}', now());`; try { await conn.promise().query(query); return JSON.stringify(data);