diff --git a/.gitignore b/.gitignore index 603ea0c..33c4d5e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ client/src/environments/version.ts client/cypress/screenshots client/cypress/results client/cypress/videos +client/documentation cypress.env.json sasjsbuild sasjsresults diff --git a/client/src/app/_globals.ts b/client/src/app/_globals.ts index 5c95c35..0754b0d 100644 --- a/client/src/app/_globals.ts +++ b/client/src/app/_globals.ts @@ -1,5 +1,8 @@ import { QueryClause } from './models/TableData' +/** + * Filtering cache info, to be reused when filtering modal is re-open + */ interface FilterCache { cols: any[] vals: any[] @@ -10,12 +13,18 @@ interface FilterCache { query: QueryClause[] } +/** + * Filtering cache info in the open viewboxes, to be reused when filtering modal is re-open + */ interface ViewboxCache { [key: number]: { filter: FilterCache } } +/** + * Initial values when no cached values stored + */ export const initFilter: { filter: FilterCache } = { filter: { cols: [], @@ -28,6 +37,13 @@ export const initFilter: { filter: FilterCache } = { } } +/** + * Cached filtering values across whole app (editor, viewer, viewboxes) + * Cached lineage libraries, tables + * Cached metadata tree + * Cached usernav tree + * Cached viyaApi collections, search and selected endpoint + */ export const globals: { rootParam: string editor: any diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 63758f3..1df0e30 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -57,24 +57,15 @@ export class AppComponent { private elementRef: ElementRef ) { this.parseDcAdapterSettings() + + /** + * Prints app info in the console such as: + * - Adapter versions + * - App version + * - Build timestamp + * + */ ;(window as any).appinfo = () => { - const licenseKeyData = this.licenceService.getLicenseKeyData() - - if (licenseKeyData) { - const expiry_date = moment( - licenseKeyData.valid_until, - 'YYYY-MM-DD' - ).startOf('day') - const current_date = moment().startOf('day') - const daysToExpiry = expiry_date.diff(current_date, 'days') - - licenseKeyData.valid_until += ` (${daysToExpiry} ${ - daysToExpiry === 1 ? 'day' : 'days' - } remaining)` - - if (isNaN(daysToExpiry)) licenseKeyData.valid_until = 'Unlimited' - } - console.table({ 'Adapter version': VERSION.adapterVersion || 'n/a', 'App version': (VERSION.tag || '').replace('v', ''), @@ -87,7 +78,12 @@ export class AppComponent { this.subscribeToLicenseEvents() + /** + * Fetches git tag ang git hash from `version.ts` file + * It's placed in the user drop down. + */ this.commitVer = (VERSION.tag || '').replace('v', '') + '.' + VERSION.hash + router.events.subscribe((val) => { this.routeUrl = this.router.url @@ -127,8 +123,10 @@ export class AppComponent { this.subscribeToAppActive() this.subscribeToDemoLimitModal() - /* In Viya streaming apps, content is served within an iframe. This code - makes that iframe "full screen" so it looks like a regular window. */ + /** + * In Viya streaming apps, content is served within an iframe. This code + * makes that iframe "full screen" so it looks like a regular window. + */ if (window.frameElement) { window.frameElement.setAttribute( 'style', @@ -143,6 +141,9 @@ export class AppComponent { } } + /** + * Parses adapter settings that are found in the tag inside index.html + */ private parseDcAdapterSettings() { const sasjsElement = document.querySelector('sasjs') @@ -180,9 +181,14 @@ export class AppComponent { this.appService.sasServiceInit() } + /** + * Opens licence page with the active licence problem + * Problem details are encoded in the url + */ public licenceProblemDetails(url: string) { this.router.navigateByUrl(url) } + /** * Based on string provided we return true, false or null * True -> Compute API @@ -199,6 +205,12 @@ export class AppComponent { return value === 'true' || false } + /** + * Listens for an `demo limit` event that will show the `Feature locked modal` + * For exmaple when in editor upload feature is not enabled + * When user tries to upload the excel, editor component will trgger this event + * And stop the execution of file upload code. + */ public subscribeToDemoLimitModal() { this.eventService.onDemoLimitModalShow.subscribe((featureName: string) => { this.demoLimitNotice = { @@ -208,6 +220,10 @@ export class AppComponent { }) } + /** + * Listens for licence events so banner can be displayed. + * App is free tier, licence will expire, is expired or is invalid + */ public subscribeToLicenseEvents() { this.licenceService.isAppFreeTier.subscribe((isAppFreeTier: boolean) => { this.freeTierBanner = isAppFreeTier @@ -227,6 +243,10 @@ export class AppComponent { ) } + /** + * Listens for an event that will activate od deactivate full application. + * Based on licence key prcoessing result + */ public subscribeToAppActive() { this.licenceService.isAppActivated.subscribe((value: any) => { this.appActive = value @@ -248,31 +268,51 @@ export class AppComponent { }) } + /** + * When startupservice request is finished with valid response, this event will + * make sure loading screen is gone. + */ public subscribeToStartupData() { this.eventService.onStartupDataLoaded.subscribe(() => { this.startupDataLoaded = true }) } + /** + * Opens requests modal when requested from event service + */ public subscribeToRequestsModal() { this.eventService.onRequestsModalOpen.subscribe((value: boolean) => { this.requestsModal = true }) } + /** + * Closes abort modal with matching ID (there could be multiple abort modals open) + */ public closeAbortModal(abortId: number) { let abortIndex = this.sasjsAborts.findIndex((abort) => abort.id === abortId) this.sasjsAborts.splice(abortIndex, 1) } + /** + * Toggles sidebar when requested from event service + */ public toggleSidebar() { this.eventService.toggleSidebar() } + /** + * Whether or not current route includes the route from param + * @param route route to check + */ public isMainRoute(route: string): boolean { return this.router.url.includes(route) } + /** + * Opens a page for updating the licence. + */ public openLicencingPage() { this.router.navigateByUrl('/licensing/update') } diff --git a/client/src/app/app.routing.ts b/client/src/app/app.routing.ts index 01b2ca8..fd7849f 100644 --- a/client/src/app/app.routing.ts +++ b/client/src/app/app.routing.ts @@ -18,6 +18,9 @@ import { DeployModule } from './deploy/deploy.module' import { LicensingModule } from './licensing/licensing.module' import { SystemModule } from './system/system.module' +/** + * Defining routes + */ export const ROUTES: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { @@ -25,6 +28,9 @@ export const ROUTES: Routes = [ loadChildren: () => ViewerModule }, { + /** + * Load review module (approve, history, submitted) + */ path: 'review', component: ReviewRouteComponent, children: [ @@ -41,6 +47,9 @@ export const ROUTES: Routes = [ }, { path: 'home', component: HomeComponent }, { + /** + * Load editor module with subroutes + */ path: 'editor', loadChildren: () => EditorModule }, @@ -59,6 +68,9 @@ export const ROUTES: Routes = [ { path: '**', component: NotFoundComponent } ] +/** + * Exporting routes + */ export const ROUTING: ModuleWithProviders = RouterModule.forRoot( ROUTES, { useHash: true } diff --git a/client/src/app/deploy/deploy.component.ts b/client/src/app/deploy/deploy.component.ts index 85cfbc7..50a4cd8 100644 --- a/client/src/app/deploy/deploy.component.ts +++ b/client/src/app/deploy/deploy.component.ts @@ -78,6 +78,9 @@ export class DeployComponent implements OnInit { this.setDeployDefaults() } + /** + * Setting default values used for deploy request + */ public setDeployDefaults() { this.dcPath = this.dcAdapterSettings?.dcPath || '' this.selectedAdminGroup = this.dcAdapterSettings?.adminGroup || '' @@ -86,6 +89,9 @@ export class DeployComponent implements OnInit { } } + /** + * Accepting terms of service shows next screen + */ public termsAgreeChange() { if (!this.autodeploy) { this.getAdminGroups() @@ -94,6 +100,9 @@ export class DeployComponent implements OnInit { this.step++ } + /** + * Fetches admin groups from VIYA to be selected for a backend deploy + */ public getAdminGroups() { fetch( this.sasJsConfig.serverUrl + '/identities/groups?sortBy=name&limit=5000', diff --git a/client/src/app/deploy/sections/automatic/automatic.component.ts b/client/src/app/deploy/sections/automatic/automatic.component.ts index 167ce02..8bc4524 100644 --- a/client/src/app/deploy/sections/automatic/automatic.component.ts +++ b/client/src/app/deploy/sections/automatic/automatic.component.ts @@ -55,6 +55,13 @@ export class AutomaticComponent implements OnInit { ngOnInit(): void {} + /** + * Executes sas.json file to deploy the backend + * Method will first try to run the `auto deploy` + * If that fails the rest of the code is ignored. + * If request is successfull, method will continue to try + * to create database if checkbox is toggled on + */ public async executeJson() { this.autodeploying = true this.isSubmittingJson = true @@ -99,6 +106,9 @@ export class AutomaticComponent implements OnInit { } } + /** + * Runs the `makedata` request sending the ADMIN and DCPATH values + */ public createDatabase() { let data = { fromjs: [ diff --git a/client/src/app/deploy/sections/manual/manual.component.ts b/client/src/app/deploy/sections/manual/manual.component.ts index 0def16c..44aa5ea 100644 --- a/client/src/app/deploy/sections/manual/manual.component.ts +++ b/client/src/app/deploy/sections/manual/manual.component.ts @@ -52,6 +52,9 @@ export class ManualComponent implements OnInit { ngOnInit(): void {} + /** + * FIXME: Remove + */ public async executableContext() { // getExecutableContexts now need AuthConfig parameter which we don't have on web // this.contextsLoading = true @@ -65,10 +68,16 @@ export class ManualComponent implements OnInit { // this.contextsLoading = false } + /** + * Removes sas.json file attached to the input + */ public clearUploadInput(event: Event) { this.deployService.clearUploadInput(event) } + /** + * Reads attached SAS file to be sent to sas for execution (backend deploy) + */ public onSasFileChange(event: any) { this.preloadedFile = false @@ -93,12 +102,18 @@ export class ManualComponent implements OnInit { fileReader.readAsText(file) } + /** + * Reads attached JSON file to be sent to sas for execution (backend deploy) + */ public async onJsonFileChange(event: any) { let file = event.target.files[0] this.jsonFile = await this.deployService.readFile(file) } + /** + * Appending precode lines to the attached sas or json file for backend deploy + */ public addPrecodeLines() { let headerLines = [ `%let context=${this.selectedContext};`, @@ -110,6 +125,9 @@ export class ManualComponent implements OnInit { this.linesOfCode.unshift(...headerLines) } + /** + * Downloadng file with precode included + */ public downloadSasPrecodeFile() { let linesAsText = this.linesOfCode.join('\n') let filename = this.fileName.split('.')[0] @@ -117,6 +135,9 @@ export class ManualComponent implements OnInit { this.downloadFile(linesAsText, filename, 'sas') } + /** + * Used for downloading log and repsonse as a file + */ public downloadFile( content: any, filename: string, @@ -125,10 +146,17 @@ export class ManualComponent implements OnInit { this.deployService.downloadFile(content, filename, extension) } + /** + * Saving dcpath to localstorage + * FIXME: maybe it'snot necessary + */ public saveDcPath() { localStorage.setItem('deploy_dc_loc', this.dcPath) } + /** + * Send sas.json to be executed (deploying backend) + */ public async executeJson() { this.isSubmittingJson = true @@ -162,6 +190,9 @@ export class ManualComponent implements OnInit { this.isSubmittingJson = false } + /** + * Send sas file to be executed (deploying backend) + */ public async executeSAS() { this.executingScript = true this.jobLog = '' @@ -194,6 +225,10 @@ export class ManualComponent implements OnInit { } } + /** + * Running makedata service + * @param newTab open and run in new tab + */ public createDatabase(newTab: boolean = true) { if (newTab) { let url = diff --git a/client/src/app/deploy/sections/sasjs-configurator/sasjs-configurator.component.ts b/client/src/app/deploy/sections/sasjs-configurator/sasjs-configurator.component.ts index f0b6970..ad0e2dc 100644 --- a/client/src/app/deploy/sections/sasjs-configurator/sasjs-configurator.component.ts +++ b/client/src/app/deploy/sections/sasjs-configurator/sasjs-configurator.component.ts @@ -5,7 +5,6 @@ import { ServerType } from '@sasjs/utils/types/serverType' import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings' import { SASGroup } from 'src/app/models/sas/public-getgroups.model' import { SASjsApiServerInfo } from 'src/app/models/sasjs-api/SASjsApiServerInfo.model' -import { HelperService } from 'src/app/services/helper.service' import { SasService } from 'src/app/services/sas.service' import { SasjsService } from 'src/app/services/sasjs.service' @@ -51,6 +50,9 @@ export class SasjsConfiguratorComponent implements OnInit { this.getServerInfo() } + /** + * Fethes the sasjs server instance info + */ getServerInfo() { this.sasjsService .getServerInfo() @@ -59,6 +61,9 @@ export class SasjsConfiguratorComponent implements OnInit { }) } + /** + * Fetches user groups from the `usernav/usergroupsbymember` service + */ getUserGroups() { this.loading = true @@ -99,6 +104,9 @@ export class SasjsConfiguratorComponent implements OnInit { ) } + /** + * Creating database + */ makeData() { // const _debug = "&_debug=131"; //debug on const _debug = ' ' //debug off diff --git a/client/src/app/directives/drag-ndrop.directive.ts b/client/src/app/directives/drag-ndrop.directive.ts index 24926d9..ae36df6 100644 --- a/client/src/app/directives/drag-ndrop.directive.ts +++ b/client/src/app/directives/drag-ndrop.directive.ts @@ -14,7 +14,9 @@ export class DragNdropDirective { @Output() fileDropped = new EventEmitter() @Output() fileDraggedOver = new EventEmitter() - // Dragover listener + /** + * Dragover listener + */ @HostListener('dragover', ['$event']) onDragOver(event: any) { event.preventDefault() @@ -26,7 +28,9 @@ export class DragNdropDirective { } } - // Dragleave listener + /** + * Dragleave listener + */ @HostListener('dragleave', ['$event']) public onDragLeave(event: any) { event.preventDefault() @@ -34,7 +38,9 @@ export class DragNdropDirective { this.fileOver = false } - // Drop listener + /** + * Drop listener + */ @HostListener('drop', ['$event']) public ondrop(event: any) { event.preventDefault() @@ -48,6 +54,9 @@ export class DragNdropDirective { } } + /** + * Checks wether dragging element contain files + */ private containsFiles(event: any) { if (event && event.dataTransfer && event.dataTransfer.types) { for (let i = 0; i < event.dataTransfer.types.length; i++) { diff --git a/client/src/app/directives/file-drop.directive.ts b/client/src/app/directives/file-drop.directive.ts index 056501a..bc3d7c5 100644 --- a/client/src/app/directives/file-drop.directive.ts +++ b/client/src/app/directives/file-drop.directive.ts @@ -22,6 +22,9 @@ export class FileDropDirective { this.element = element } + /** + * Dragging element drop event + */ @HostListener('drop', ['$event']) onDrop(event: DragEvent): void { this._preventAndStop(event) @@ -39,6 +42,9 @@ export class FileDropDirective { this.fileDrop.emit(fileList) } + /** + * Dragging element drag over event + */ @HostListener('dragover', ['$event']) onDragOver(event: DragEvent): void { this._preventAndStop(event) @@ -59,6 +65,10 @@ export class FileDropDirective { this.fileOver.emit(false) } + /** + * Prevent propagation trough elements and stop default behavior + * For particular event + */ protected _preventAndStop(event: MouseEvent): void { event.preventDefault() event.stopPropagation() diff --git a/client/src/app/directives/file-select.directive.ts b/client/src/app/directives/file-select.directive.ts index acf20ef..f65ecb8 100644 --- a/client/src/app/directives/file-select.directive.ts +++ b/client/src/app/directives/file-select.directive.ts @@ -21,6 +21,9 @@ export class FileSelectDirective { this.element = element } + /** + * Checks if files exist in the input after input change + */ isEmptyAfterSelection(): boolean { return !!this.element.nativeElement.attributes.multiple } diff --git a/client/src/app/editor/editor.component.ts b/client/src/app/editor/editor.component.ts index 86d14a2..86eb2a4 100644 --- a/client/src/app/editor/editor.component.ts +++ b/client/src/app/editor/editor.component.ts @@ -368,6 +368,9 @@ export class EditorComponent implements OnInit, AfterViewInit { this.setRestrictions() } + /** + * Prepare feature restrictions based on licence key + */ private parseRestrictions() { this.restrictions.restrictAddRecord = this.licenceState.value.addRecord === false @@ -377,6 +380,10 @@ export class EditorComponent implements OnInit, AfterViewInit { this.licenceState.value.fileUpload === false } + /** + * Applying prepared restrictions + * @param overrideRestrictions can be used to apply and override specific restrictions + */ private setRestrictions(overrideRestrictions?: EditorRestrictions) { if (overrideRestrictions) { this.restrictions = { @@ -396,6 +403,9 @@ export class EditorComponent implements OnInit, AfterViewInit { } } + /** + * Disabling add row button based on wether rows limit is present + */ private checkRowLimit() { if (this.columnLevelSecurityFlag) return @@ -410,12 +420,19 @@ export class EditorComponent implements OnInit, AfterViewInit { } } + /** + * Resetting filter variables + */ public resetFilter() { if (this.queryFilterCompList.first) { this.queryFilterCompList.first.resetFilter() } } + /** + * Openning file upload modal + * If feature is locked, `feature locked` modal will be shown + */ public onShowUploadModal() { if (this.restrictions.restrictFileUpload) { this.eventService.showDemoLimitModal('File Upload') @@ -433,6 +450,10 @@ export class EditorComponent implements OnInit, AfterViewInit { if (!this.uploadPreview) this.showUploadModal = true } + /** + * Called by FileDropDirective + * @param e true if file is dragged over the drop zone + */ public fileOverBase(e: boolean): void { this.hasBaseDropZoneOver = e } @@ -614,6 +635,10 @@ export class EditorComponent implements OnInit, AfterViewInit { return returnObj } + /** + * When excel is password protected we will display the password promppt for user to type password in. + * @returns Password user input or undefined if discarded by user + */ public promptExcelPassword(): Promise { return new Promise((resolve, reject) => { this.filePasswordModal = true @@ -639,6 +664,13 @@ export class EditorComponent implements OnInit, AfterViewInit { }) } + /** + * Parses attached file, to be uploaded + * If attached file is CSV it will be send to backend straight away + * If attached file is EXCEL it will be displayed in the table, in preview mode + * @param event file drop event + * @param dropped whether it's dropped or added by browse button + */ public getFileDesc(event: any, dropped: boolean = false) { this.excelUploadState = 'Loading' this.excelFileParsing = true @@ -1004,6 +1036,9 @@ export class EditorComponent implements OnInit, AfterViewInit { } } + /** + * Submits attached excel file that is in preview mode + */ public submitExcel() { if (this.licenceState.value.submit_rows_limit !== Infinity) { this.submitLimitNotice = true @@ -1013,6 +1048,9 @@ export class EditorComponent implements OnInit, AfterViewInit { this.getFile() } + /** + * This method will run validations and upload all of the pending files that are in the uploader queue + */ public getFile() { if (this.checkInvalid()) { this.eventService.showAbortModal(null, 'Invalid values are present.') @@ -1085,6 +1123,9 @@ export class EditorComponent implements OnInit, AfterViewInit { ) } + /** + * After excel file is attached and parsed, this function will display it's content in the HOT table in read only mode + */ public getPendingExcelPreview() { this.queryTextSaved = this.queryText this.queryText = '' @@ -1176,6 +1217,10 @@ export class EditorComponent implements OnInit, AfterViewInit { // ) } + /** + * Drops the attached excel file + * @param discardData wheter to discard data parsed from the file or to keep it in the table after dropping a attached excel file + */ public discardPendingExcel(discardData?: boolean) { this.hotInstance.updateSettings({ maxRows: this.licenceState.value.editor_rows_allowed @@ -1199,6 +1244,10 @@ export class EditorComponent implements OnInit, AfterViewInit { } } + /** + * Drops attached excel file, keeps it's data in the DC table + * User can now edit the table and submit. Witout the file present. + */ public previewTableEditConfirm() { this.discardPendingExcel() this.convertToCorrectTypes(this.dataSource) diff --git a/client/src/app/models/ErrorBody.ts b/client/src/app/models/ErrorBody.ts new file mode 100644 index 0000000..c346045 --- /dev/null +++ b/client/src/app/models/ErrorBody.ts @@ -0,0 +1,5 @@ +export interface ErrorBody { + message: string + details: any + raw: any +} \ No newline at end of file diff --git a/client/src/app/query/query.component.ts b/client/src/app/query/query.component.ts index 4594be0..f3644da 100644 --- a/client/src/app/query/query.component.ts +++ b/client/src/app/query/query.component.ts @@ -137,6 +137,11 @@ export class QueryComponent public whereClause: string | undefined public logicOperators: Array = ['AND', 'OR'] + /** + * Temporary values array used in pickers + * because they need particular format to work + * before sending values to backend, values are parsed + */ public queryDateTime: QueryDateTime[] = [] public currentClauseIndex: number = -1 @@ -158,6 +163,11 @@ export class QueryComponent } } + /** + * Gets and sets temporary values selected with DATETIME or TIME picker + * Those values are used for picker to work with format it lieks + * Later before sending values to backend, values are parsed + */ getQueryDateTime(clauseIndex: number, queryIndex: number): QueryDateTime { let existingQueryDateTime = this.queryDateTime.find( (x) => x.clauseIndex === clauseIndex && x.queryIndex === queryIndex @@ -178,10 +188,16 @@ export class QueryComponent return existingQueryDateTime } + /** + * When toggling pickers feature we reset the temp picker values array + */ usePickersChange() { this.queryDateTime = [] } + /** + * Resets all variables used for filtering + */ public resetFilter() { this.whereString = undefined this.whereClause = undefined @@ -210,6 +226,10 @@ export class QueryComponent this.whereClauseFn(true) } + /** + * `Globals` are used to store filtering state (variables) as a caching feature + * until browser reloads + */ public setToGlobals() { if (!this.caching) return @@ -237,6 +257,10 @@ export class QueryComponent console.log('globals', globals) } + /** + * `Globals` are used to store filtering state (variables) as a caching feature + * until browser reloads + */ public getFromGlobals() { if (!this.caching) return @@ -269,6 +293,11 @@ export class QueryComponent } } + /** + * Sets filtering multiple caluses group logic (and / or) + * + * @param groupLogic to set + */ public setGroupLogic(groupLogic: any) { this.groupLogic = groupLogic this.clauses.groupLogic = groupLogic diff --git a/client/src/app/services/helper.service.ts b/client/src/app/services/helper.service.ts index 1b59150..0a0bd29 100644 --- a/client/src/app/services/helper.service.ts +++ b/client/src/app/services/helper.service.ts @@ -17,6 +17,17 @@ export class HelperService { console.log('Is IE or Edge?', this.isMicrosoft) } + /** + * Converts a JavaScript date object to a SAS Date or Datetime, given the logic below: + * + * A JS Date contains the number of _milliseconds_ since 01/01/1970 + * A SAS Date contains the number of _days_ since 01/01/1960 + * A SAS Datetime contains the number of _seconds_ since 01/01/1960 + * + * @param jsDate JS Date to be converted. The type is instance of `Date` + * @param unit Unit in which to return the SAS Date / datetime, eg `sasdate | sasdatetime` + * @returns SAS Date value based on `unit` param + */ public convertJsDateToSasDate( jsDate: string | Date, unit: string = 'days' @@ -63,6 +74,17 @@ export class HelperService { return 0 } + /** + * Converts a SAS Date or Datetime to a JavaScript date object, given the logic below: + * + * A JS Date contains the number of _milliseconds_ since 01/01/1970 + * A SAS Date contains the number of _days_ since 01/01/1960 + * A SAS Datetime contains the number of _seconds_ since 01/01/1960 + * + * @param sasValue SAS Date or Datetime to be converted. The type could be `number` or `string. + * @param unit Unit from which to convert the SAS Date / Datetime, eg `sasdate | sasdatetime` + * @returns JavaScript Date object + */ public convertSasDaysToJsDate( sasValue: number | string, unit: string = 'days' @@ -87,6 +109,11 @@ export class HelperService { return new Date(msNegativeTenYears + sasValue * msInDay) } + /** + * + * @param array all elements in the clarity tree + * @param arrToFilter sub array in the tree to be filtered for example `tables` + */ public treeOnFilter(array: any, arrToFilter: string) { let search = array['searchString'] ? array['searchString'] : '' let arrToFilterArray = arrToFilter.split('.')[0] diff --git a/client/src/app/services/sas-store.service.ts b/client/src/app/services/sas-store.service.ts index 92b9a41..b1bb361 100644 --- a/client/src/app/services/sas-store.service.ts +++ b/client/src/app/services/sas-store.service.ts @@ -40,6 +40,16 @@ export class SasStoreService { private loggerService: LoggerService ) {} + /** + * Wrapper for making request to service + * Should be removed, as it's redundant now + * TODO: Refactor to call editors/getdata directly + * @param tableData + * @param tableName + * @param program + * @param libds + * @returns + */ public async callService( tableData: Array, tableName: string, @@ -60,6 +70,15 @@ export class SasStoreService { return response } + /** + * Calling editors/stagedata - saving table data, sending request to backend + * @param tableParams params to send to backend + * @param tableData data to be updated + * @param tableName name of the table to be updated + * @param program service against which we send request + * @param $dataFormats column data formats recieved from backend, sending it back + * @returns adapter.request() response + */ public async updateTable( tableParams: any, tableData: any, @@ -86,6 +105,13 @@ export class SasStoreService { return res } + /** + * Sending request to 'approvers/getapprovals' to fetch approvals list + * @param tableData sending to backend table data + * @param tableName sending to backend table name + * @param program service to run request on + * @returns HTTP Response + */ public async getApprovals( tableData: any, tableName: string, @@ -96,6 +122,13 @@ export class SasStoreService { let res: any = await this.sasService.request(program, tables) return res } + + /** + * Interceptor for loading of the submitted details + * @param detail submitter + * @param index submitter index + * @param data submit data + */ public async sendDetails(detail: any, index: any, data: any) { let details = Object.assign({ sub: true }, detail) let subData = data[index] @@ -105,11 +138,20 @@ export class SasStoreService { } this.submittDetail.next(allData) } + + /** + * + * @returns All submits + */ public async getSubmitts() { let res: any = await this.sasService.request('editors/getsubmits', null) return res } + /** + * + * @returns All libraries + */ public async viewLibs() { return this.sasService.request('public/viewlibs', null) } diff --git a/client/src/app/services/sas.service.ts b/client/src/app/services/sas.service.ts index 4006b7b..1a12523 100644 --- a/client/src/app/services/sas.service.ts +++ b/client/src/app/services/sas.service.ts @@ -13,6 +13,7 @@ import { DcAdapterSettings } from '../models/DcAdapterSettings' import { AppStoreService } from './app-store.service' import { LoggerService } from './logger.service' import { RequestWrapperOptions } from '../models/RequestWrapperOptions' +import { ErrorBody } from '../models/ErrorBody' @Injectable({ providedIn: 'root' @@ -39,6 +40,11 @@ export class SasService { private router: Router ) {} + /** + * Same as `setup` function in the sasjs.service, this is the constructor replacement. + * This function is being called by `app.service`. + * Because of timing and dependency issues + */ public sasServiceInit() { this.dcAdapterSettings = this.appStoreService.getDcAdapterSettings() @@ -80,6 +86,16 @@ export class SasService { } } + /** + * Runing a backend request against a service. + * Function also handles the displaying of success or error modals. + * + * @param url service to run reuqest against + * @param data to be sent to backend service + * @param config additional parameters to force eg. { debug: false } + * @param wrapperOptions used to suppress error or success abort modals after request is finished + * @returns + */ public request( url: string, data: any, @@ -197,6 +213,14 @@ export class SasService { }) } + /** + * Uploads a file to the backend, using the adapter upload function. + * + * @param sasService Service to which the file will be sent + * @param files Files to be sent + * @param params Aditional parameters eg. { debug: false } + * @returns HTTP Response + */ public uploadFile(sasService: string, files: UploadFile[], params: any) { return this.sasjsAdapter.uploadFile(sasService, files, params) } @@ -487,9 +511,3 @@ export class SasService { } } } - -interface ErrorBody { - message: string - details: any - raw: any -} diff --git a/client/src/app/services/sasjs.service.ts b/client/src/app/services/sasjs.service.ts index 65a6646..2848929 100644 --- a/client/src/app/services/sasjs.service.ts +++ b/client/src/app/services/sasjs.service.ts @@ -25,6 +25,12 @@ export class SasjsService { private appStoreService: AppStoreService ) {} + /** + * This function is replacing the constructor. + * The reason for this is timing issues, other services eg. sas.service, app-store.service + * must be initialized before this bit of code is executed. + * This function is being called by `sas.service` + */ setup() { const adapterConfig = this.appStoreService.getDcAdapterSettings() @@ -32,10 +38,18 @@ export class SasjsService { this.driveUrl = `${this.url}/drive` } + /** + * + * @returns Sasjs/server information + */ getServerInfo(): Observable { return this.http.get(`${this.url}/info`) } + /** + * Gets file contents on a given path + * @param filePath path to the file + */ getFileFromDrive(filePath: string) { return this.http.get( `${this.driveUrl}/file/?_filePath=${filePath}`, @@ -43,6 +57,11 @@ export class SasjsService { ) } + /** + * Gets folder contents on a given path + * @param folderPath path to the folder + * @returns + */ getFolderContentsFromDrive( folderPath: string ): Observable { diff --git a/client/src/app/shared/abort-modal/info-modal.component.ts b/client/src/app/shared/abort-modal/info-modal.component.ts index 0cc7f53..c679642 100644 --- a/client/src/app/shared/abort-modal/info-modal.component.ts +++ b/client/src/app/shared/abort-modal/info-modal.component.ts @@ -41,6 +41,14 @@ export class InfoModalComponent implements OnInit { this.data = newData } + /** + * Wheter or not to show the `Open configurator button` + * Button used for navigating to the `configuration` page + * Only for SAS9 + * @param sasService backend service that caused this info modal to be shown + * Decision is made based on that service path + * @returns + */ showConfiguratorButton(sasService: string | null) { const sasjsConfig = this.sasService.getSasjsConfig() @@ -54,6 +62,9 @@ export class InfoModalComponent implements OnInit { this.onConfirmModalClick.emit() } + /** + * Only on SAS9, opening a backend configurator/deploy page + */ openConfigurator() { this.eventService.startupDataLoaded() this.router.navigateByUrl('/deploy') diff --git a/client/src/app/viewer/viewer.component.ts b/client/src/app/viewer/viewer.component.ts index 1387a6f..afe257e 100644 --- a/client/src/app/viewer/viewer.component.ts +++ b/client/src/app/viewer/viewer.component.ts @@ -201,16 +201,28 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { ) } + /** + * Open viewboxes modal + */ public newViewbox() { this.viewboxOpen = true } + /** + * Resetting filter variables + */ public resetFilter() { if (this.queryFilterCompList.first) { this.queryFilterCompList.first.resetFilter() } } + /** + * Searching table against particular string, data is comming from backend. + * There is also a toggle that will search for a numeric values + * + * @param inputElement input from which search string is captured + */ public async searchTable(inputElement: any) { this.searchLoading = true @@ -249,6 +261,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { this.searchLoading = false } + /** + * Re sending request to backend and re-setting data in the HOT + */ public reloadTableData() { this.viewData(this.urlFilterPk || 0) } @@ -278,6 +293,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { ) } + /** + * FIXME: Should be removed, not used + */ public filterFn(input: string) { let libraries = this.libraries this.libraries = libraries.filter( @@ -286,6 +304,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { ) } + /** + * Downloads file from backend, against `getrawdata` service, link is created and open in new tab + */ public downloadData() { let storage = this.sasjsConfig.serverUrl let metaData = this.sasjsConfig.appLoc @@ -320,6 +341,9 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { this.openDownload = false } + /** + * Downloads file from backend, against `getddl` service, link is created and open in new tab + */ public downloadDDL() { let libref = this.lib let ds = this.table @@ -346,15 +370,27 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { this.openDownload = false } + /** + * When clicked on textare in the Web Query Modal, this function will + * select all text inside. + * @param evt textarea which contains the web query text + */ public onCliCommandFocus(evt: any): void { evt.preventDefault() evt.target.select() } + /** + * Navigate to the edit page of a viewing table + */ public editTable() { this.router.navigateByUrl('/editor/' + this.libTab) } + /** + * Used to show/hide the edit table button + * @returns Wheter currently viewed table is edtiable + */ public tableEditExists() { let editTables: any = {} editTables = globals.editor.libsAndTables @@ -367,12 +403,18 @@ export class ViewerComponent implements AfterContentInit, AfterViewInit { return editTables[currentLibrary].includes(currentTable) } + /** + * Navigate to the lineage of a viewing table + */ public goToLineage() { let routeUri = this.tableuri!.split('\\')[1] let lineageUrl = `/view/lineage/${routeUri}/REVERSE` this.router.navigateByUrl(lineageUrl) } + /** + * Displays web query modal + */ public showWebQuery() { this.webQuery = true let filter_pk: number