diff --git a/src/modules/reports/shared/configs/general-report/configs/sample.report.ts b/src/modules/reports/shared/configs/general-report/configs/sample.report.ts new file mode 100644 index 0000000..cabbd03 --- /dev/null +++ b/src/modules/reports/shared/configs/general-report/configs/sample.report.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.general_report, + unique_name: `${REPORT_GROUP.general_report}__sample`, + label: 'Sample General Report ', + table_schema: 'season_types', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__update_at', + query: 'main.update_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/general-report/index.ts b/src/modules/reports/shared/configs/general-report/index.ts index 9d6a412..1bba05d 100644 --- a/src/modules/reports/shared/configs/general-report/index.ts +++ b/src/modules/reports/shared/configs/general-report/index.ts @@ -1 +1,5 @@ -export const GeneralReportConfig = []; +import { ReportConfigEntity } from '../../entities/report-config.entity'; + +import SampleReport from './configs/sample.report'; + +export const GeneralReportConfig: ReportConfigEntity[] = [SampleReport]; diff --git a/src/modules/reports/shared/configs/index.ts b/src/modules/reports/shared/configs/index.ts index 1ec9aab..603161a 100644 --- a/src/modules/reports/shared/configs/index.ts +++ b/src/modules/reports/shared/configs/index.ts @@ -1,4 +1,8 @@ +import { ReportConfigEntity } from '../entities/report-config.entity'; import { GeneralReportConfig } from './general-report'; import { TenantReportConfig } from './tenant-report'; -export const ReportConfigs = [...GeneralReportConfig, ...TenantReportConfig]; +export const ReportConfigs: ReportConfigEntity[] = [ + ...GeneralReportConfig, + ...TenantReportConfig, +]; diff --git a/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts b/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts new file mode 100644 index 0000000..2c223fe --- /dev/null +++ b/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts @@ -0,0 +1,39 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.tenant_report, + unique_name: `${REPORT_GROUP.tenant_report}__sample`, + label: 'Sample Tenant Report ', + table_schema: 'season_types', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__update_at', + query: 'main.update_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/tenant-report/index.ts b/src/modules/reports/shared/configs/tenant-report/index.ts index 3893e35..dfe07b1 100644 --- a/src/modules/reports/shared/configs/tenant-report/index.ts +++ b/src/modules/reports/shared/configs/tenant-report/index.ts @@ -1 +1,4 @@ -export const TenantReportConfig = []; +import { ReportConfigEntity } from '../../entities/report-config.entity'; +import SampleReport from './configs/sample.report'; + +export const TenantReportConfig: ReportConfigEntity[] = [SampleReport]; diff --git a/src/modules/reports/shared/constant/report-group.constant.ts b/src/modules/reports/shared/constant/report-group.constant.ts index 80476fa..3fc0b60 100644 --- a/src/modules/reports/shared/constant/report-group.constant.ts +++ b/src/modules/reports/shared/constant/report-group.constant.ts @@ -1,5 +1,6 @@ export enum REPORT_GROUP { // PATTERN => MODULE__MENU__SUB_MENU // EXAMPLE: - contact__reports = 'contact__reports', + general_report = 'general_report', + tenant_report = 'tenant_report', } diff --git a/src/modules/reports/shared/entities/report-config.entity.ts b/src/modules/reports/shared/entities/report-config.entity.ts index 90dc77f..bca0f5c 100644 --- a/src/modules/reports/shared/entities/report-config.entity.ts +++ b/src/modules/reports/shared/entities/report-config.entity.ts @@ -55,5 +55,5 @@ export interface ReportConfigEntity { filter_configs?: FilterConfigEntity[]; filter_period_config?: FilterPeriodConfigEntity; ignore_filter_keys?: string[]; - customQueryFilter?(column: string): string; + customQueryColumn?(column: string): string; } diff --git a/src/modules/reports/shared/helpers/query-builder.ts b/src/modules/reports/shared/helpers/query-builder.ts index 458bfe1..825b76f 100644 --- a/src/modules/reports/shared/helpers/query-builder.ts +++ b/src/modules/reports/shared/helpers/query-builder.ts @@ -8,6 +8,7 @@ import { export class ReportQueryBuilder { public reportConfig: ReportConfigEntity; public queryModel: QueryModelEntity; + constructor(reportConfig: ReportConfigEntity, queryModel: QueryModelEntity) { this.reportConfig = reportConfig; this.queryModel = queryModel; @@ -16,53 +17,73 @@ export class ReportQueryBuilder { getBaseConfig() { const tableSchema = this.reportConfig.table_schema; const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; - const queryModel = this.queryModel; + + const selectSql = this.createSelectSql(); + const selectSqlExport = this.createSelectSqlExport(); + + const whereSql = this.createWhereSql(); + const limitSql = this.createLimitSql(); + + const orderBySql = this.createOrderBySql(); + const orderBySqlExport = this.createOrderBySqlExport(); + + const groupBy = this.createGroupBySql(); + const groupByExport = this.createGroupBySqlExport(); + return { tableSchema, mainTableAlias, - queryModel, + selectSql, + selectSqlExport, + whereSql, + limitSql, + orderBySql, + orderBySqlExport, + ...groupBy, + ...groupByExport, }; } - // OLD VERSION - buildSql(reportConfig: ReportConfigEntity, queryModel: any) { - const tableSchema = reportConfig.table_schema; - const mainTableAlias = reportConfig.main_table_alias ?? 'main'; + getSql(): string { + const { + selectSql, + tableSchema, + whereSql, + groupByQuery, + orderBySql, + limitSql, + } = this.getBaseConfig(); + return `SELECT ${selectSql} FROM ${tableSchema} ${whereSql} ${groupByQuery} ${orderBySql} ${limitSql}` as string; + } - const selectSql = this.createSelectSql(queryModel, reportConfig); - const selectSqlExport = this.createSelectSqlExport( - queryModel, - reportConfig, - ); + getSqlCount(): string { + const { groupByColumn, mainTableAlias, tableSchema, whereSql } = + this.getBaseConfig(); - const whereSql = this.createWhereSql(queryModel, reportConfig); - const limitSql = this.createLimitSql(queryModel); - - const orderBySql = this.createOrderBySql(queryModel, reportConfig); - const orderBySqlExport = this.createOrderBySqlExport( - queryModel, - reportConfig, - ); - - const { groupByQuery, groupByColumn } = this.createGroupBySql( - queryModel, - reportConfig, - ); - const { groupByQueryExport, groupByColumnExport } = - this.createGroupBySqlExport(queryModel, reportConfig); - - const SQL: string = - `SELECT ${selectSql} FROM ${tableSchema} ${whereSql} ${groupByQuery} ${orderBySql} ${limitSql}` as string; - const SQL_COUNT: string = `SELECT COUNT(${ + return `SELECT COUNT(${ groupByColumn ? `DISTINCT ${groupByColumn}` : `${mainTableAlias}.id` }) ${ groupByColumn ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumn} IS NULL THEN 1 END)` : '' } AS count FROM ${tableSchema} ${whereSql}` as string; - const SQL_EXPORT: string = - `SELECT ${selectSqlExport} FROM ${tableSchema} ${whereSql} ${groupByQueryExport} ${orderBySqlExport}` as string; - const SQL_EXPORT_COUNT: string = `SELECT COUNT(${ + } + + getSqlExport(): string { + const { + selectSqlExport, + tableSchema, + whereSql, + groupByQueryExport, + orderBySqlExport, + } = this.getBaseConfig(); + return `SELECT ${selectSqlExport} FROM ${tableSchema} ${whereSql} ${groupByQueryExport} ${orderBySqlExport}` as string; + } + + getSqlCountExport(): string { + const { groupByColumnExport, mainTableAlias, tableSchema, whereSql } = + this.getBaseConfig(); + return `SELECT COUNT(${ groupByColumnExport ? `DISTINCT ${groupByColumnExport}` : `${mainTableAlias}.id` @@ -71,17 +92,70 @@ export class ReportQueryBuilder { ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumnExport} IS NULL THEN 1 END)` : '' } AS count FROM ${tableSchema} ${whereSql}` as string; - - return { SQL, SQL_COUNT, SQL_EXPORT, SQL_EXPORT_COUNT }; } - createSelectSql(queryModel: any, reportConfig: ReportConfigEntity): string { - const configColumns = reportConfig.column_configs; - const mainTableAlias = reportConfig.main_table_alias ?? 'main'; + isDoingGrouping(rowGroupCols, groupKeys) { + // we are not doing grouping if at the lowest level. we are at the lowest level + // if we are grouping by more columns than we have keys for (that means the user + // has not expanded a lowest level group, OR we are not grouping at all). + return rowGroupCols.length > groupKeys.length; + } - const rowGroupCols = queryModel.rowGroupCols; - const valueCols = queryModel.valueCols; - const groupKeys = queryModel.groupKeys; + interpolate(str, o) { + return str.replace(/{([^{}]*)}/g, function (a, b) { + const r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + } + + createLimitSql() { + const startRow = this.queryModel.startRow; + const endRow = this.queryModel.endRow; + const pageSize = endRow - startRow; + return ' LIMIT ' + (pageSize + 1) + ' OFFSET ' + startRow; + } + + getRowCount(results) { + if (results === null || results === undefined || results.length === 0) { + return null; + } + const currentLastRow = this.queryModel.startRow + results.length; + return currentLastRow <= this.queryModel.endRow ? currentLastRow : -1; + } + + cutResultsToPageSize(results) { + const pageSize = this.queryModel.endRow - this.queryModel.startRow; + if (results && results.length > pageSize) { + return results.splice(0, pageSize); + } else { + return results; + } + } + + findQueryConfig(column: string) { + const configColumns = this.reportConfig.column_configs ?? []; + + const queryColumn: ReportColumnConfigEntity = configColumns.find( + (el) => el.column === column, + ); + const customQueryColumn = this.reportConfig?.customQueryColumn + ? this.reportConfig.customQueryColumn(column) + : undefined; + + if (customQueryColumn) return customQueryColumn; + else if (queryColumn) return queryColumn.query; + + return column.replace('__', '.'); + } + + // GENERATE SELECT QUERY ============================================ + createSelectSql(): string { + const configColumns = this.reportConfig.column_configs; + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + + const rowGroupCols = this.queryModel.rowGroupCols; + const valueCols = this.queryModel.valueCols; + const groupKeys = this.queryModel.groupKeys; if (this.isDoingGrouping(rowGroupCols, groupKeys)) { const colsToSelect = []; @@ -89,14 +163,12 @@ export class ReportQueryBuilder { const rowGroupColChildCount = rowGroupCols[groupKeys.length + 1]; colsToSelect.push( - this.findQueryConfig(reportConfig, rowGroupCol.field) + - ` AS ${rowGroupCol.field}`, + this.findQueryConfig(rowGroupCol.field) + ` AS ${rowGroupCol.field}`, ); if (rowGroupColChildCount) { colsToSelect.push( `COUNT(DISTINCT ${this.findQueryConfig( - reportConfig, rowGroupColChildCount.field, )}) AS count_child_group`, ); @@ -109,7 +181,6 @@ export class ReportQueryBuilder { valueCols.forEach(function (valueCol) { colsToSelect.push( `${valueCol.aggFunc} (${thisSelf.findQueryConfig( - reportConfig, valueCol.field, )}) AS ${valueCol.field}`, ); @@ -124,14 +195,11 @@ export class ReportQueryBuilder { return columns.join(', '); } - createSelectSqlExport( - queryModel: any, - reportConfig: ReportConfigEntity, - ): string { - const configColumns = reportConfig.column_configs; + createSelectSqlExport(): string { + const configColumns = this.reportConfig.column_configs; - const rowGroupCols = queryModel.rowGroupCols; - const valueCols = queryModel.valueCols; + const rowGroupCols = this.queryModel.rowGroupCols; + const valueCols = this.queryModel.valueCols; // eslint-disable-next-line @typescript-eslint/no-this-alias const thisSelf = this; @@ -140,7 +208,7 @@ export class ReportQueryBuilder { const colsToSelect = []; rowGroupCols.forEach(function (rowGroupCol) { colsToSelect.push( - thisSelf.findQueryConfig(reportConfig, rowGroupCol.field) + + thisSelf.findQueryConfig(rowGroupCol.field) + ` AS ${rowGroupCol.field}`, ); }); @@ -148,7 +216,6 @@ export class ReportQueryBuilder { valueCols.forEach(function (valueCol) { colsToSelect.push( `${valueCol.aggFunc} (${thisSelf.findQueryConfig( - reportConfig, valueCol.field, )}) AS ${valueCol.field}`, ); @@ -161,7 +228,9 @@ export class ReportQueryBuilder { return columns.join(', '); } + // ================================================================== + // GENERATE WHERE QUERY ============================================= createFilterSql(column: string, item: { type: FILTER_TYPE; filter: any }) { switch (item.type) { // TEXT @@ -237,23 +306,22 @@ export class ReportQueryBuilder { } } - createWhereSql(queryModel, reportConfig: ReportConfigEntity) { - // const configColumns = reportConfig.column_configs ?? []; - const configFilters = reportConfig.filter_configs ?? []; - const ignoreFilterKeys = reportConfig.ignore_filter_keys ?? []; + createWhereSql() { + const configFilters = this.reportConfig.filter_configs ?? []; + const ignoreFilterKeys = this.reportConfig.ignore_filter_keys ?? []; const ignoreFilter = configFilters .filter((el) => el.hide_field) .map((el) => el.filter_column); const ignoreFilterKey = [...ignoreFilter, ...ignoreFilterKeys]; - const whereCondition = reportConfig?.whereCondition - ? reportConfig.whereCondition(queryModel.filterModel) + const whereCondition = this.reportConfig?.whereCondition + ? this.reportConfig.whereCondition(this.queryModel.filterModel) : []; - const rowGroupCols = queryModel.rowGroupCols; - const groupKeys = queryModel.groupKeys; - const filterModel = queryModel.filterModel; + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; + const filterModel = this.queryModel.filterModel; // eslint-disable-next-line @typescript-eslint/no-this-alias const thisSelf = this; @@ -263,9 +331,7 @@ export class ReportQueryBuilder { groupKeys.forEach(function (key, index) { const colName = rowGroupCols[index].field; // whereParts.push(colName + ' = "' + key + '"'); - whereParts.push( - `${thisSelf.findQueryConfig(reportConfig, colName)} = '${key}'`, - ); + whereParts.push(`${thisSelf.findQueryConfig(colName)} = '${key}'`); }); } @@ -274,14 +340,14 @@ export class ReportQueryBuilder { keySet.forEach(function (key) { if (!ignoreFilterKey.includes(key)) { const item = filterModel[key]; - const newKey = thisSelf.findQueryConfig(reportConfig, key); + const newKey = thisSelf.findQueryConfig(key); whereParts.push(thisSelf.createFilterSql(newKey, item)); } }); } // set default where conditions - const defaultConditions = reportConfig.whereDefaultConditions; + const defaultConditions = this.reportConfig.whereDefaultConditions; const defaultWhereOptions = []; if (defaultConditions) { defaultConditions.forEach((condition) => { @@ -309,15 +375,17 @@ export class ReportQueryBuilder { return ''; } } + // ================================================================== - createOrderBySql(queryModel, reportConfig: ReportConfigEntity) { - const mainTableAlias = reportConfig.main_table_alias ?? 'main'; - const defaultOrderBy = reportConfig.defaultOrderBy ?? []; - const lowLevelOrderBy = reportConfig.lowLevelOrderBy ?? []; + // GENERATE ORDER QUERY ============================================= + createOrderBySql() { + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + const defaultOrderBy = this.reportConfig.defaultOrderBy ?? []; + const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? []; - const rowGroupCols = queryModel.rowGroupCols; - const groupKeys = queryModel.groupKeys; - const sortModel = queryModel.sortModel; + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; + const sortModel = this.queryModel.sortModel; const grouping = this.isDoingGrouping(rowGroupCols, groupKeys); @@ -370,13 +438,13 @@ export class ReportQueryBuilder { } } - createOrderBySqlExport(queryModel, reportConfig: ReportConfigEntity) { - const mainTableAlias = reportConfig.main_table_alias ?? 'main'; - const defaultOrderBy = reportConfig.defaultOrderBy ?? []; - const lowLevelOrderBy = reportConfig.lowLevelOrderBy ?? []; + createOrderBySqlExport() { + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + const defaultOrderBy = this.reportConfig.defaultOrderBy ?? []; + const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? []; - const rowGroupCols = queryModel.rowGroupCols; - const sortModel = queryModel.sortModel; + const rowGroupCols = this.queryModel.rowGroupCols; + const sortModel = this.queryModel.sortModel; const defaultOrder = defaultOrderBy[0] ? defaultOrderBy @@ -400,17 +468,18 @@ export class ReportQueryBuilder { ); } } + // ================================================================== - createGroupBySql(queryModel, reportConfig: ReportConfigEntity) { - // const configColumns = reportConfig.column_configs; - const rowGroupCols = queryModel.rowGroupCols; - const groupKeys = queryModel.groupKeys; + // GENERATE GROUP BY QUERY ========================================== + createGroupBySql() { + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; if (this.isDoingGrouping(rowGroupCols, groupKeys)) { const colsToGroupBy = []; const rowGroupCol = rowGroupCols[groupKeys.length]; - colsToGroupBy.push(this.findQueryConfig(reportConfig, rowGroupCol.field)); + colsToGroupBy.push(this.findQueryConfig(rowGroupCol.field)); return { groupByQuery: ' GROUP BY ' + colsToGroupBy.join(', '), @@ -424,9 +493,8 @@ export class ReportQueryBuilder { } } - createGroupBySqlExport(queryModel, reportConfig: ReportConfigEntity) { - // const configColumns = reportConfig.column_configs; - const rowGroupCols = queryModel.rowGroupCols; + createGroupBySqlExport() { + const rowGroupCols = this.queryModel.rowGroupCols; // eslint-disable-next-line @typescript-eslint/no-this-alias const thisSelf = this; @@ -434,9 +502,7 @@ export class ReportQueryBuilder { if (rowGroupCols.length > 0) { const colsToGroupBy = []; rowGroupCols.forEach(function (rowGroupCol) { - colsToGroupBy.push( - thisSelf.findQueryConfig(reportConfig, rowGroupCol.field), - ); + colsToGroupBy.push(thisSelf.findQueryConfig(rowGroupCol.field)); }); return { @@ -450,57 +516,5 @@ export class ReportQueryBuilder { }; } } - - findQueryConfig(config: ReportConfigEntity, column: string) { - const configColumns = config.column_configs ?? []; - - const findQuery: ReportColumnConfigEntity = configColumns.find( - (el) => el.column === column, - ); - const customQuery = config?.customQueryFilter - ? config.customQueryFilter(column) - : undefined; - - if (customQuery) return customQuery; - else if (findQuery) return findQuery.query; - - return column.replace('__', '.'); - } - - isDoingGrouping(rowGroupCols, groupKeys) { - // we are not doing grouping if at the lowest level. we are at the lowest level - // if we are grouping by more columns than we have keys for (that means the user - // has not expanded a lowest level group, OR we are not grouping at all). - return rowGroupCols.length > groupKeys.length; - } - - interpolate(str, o) { - return str.replace(/{([^{}]*)}/g, function (a, b) { - const r = o[b]; - return typeof r === 'string' || typeof r === 'number' ? r : a; - }); - } - createLimitSql(queryModel) { - const startRow = queryModel.startRow; - const endRow = queryModel.endRow; - const pageSize = endRow - startRow; - return ' LIMIT ' + (pageSize + 1) + ' OFFSET ' + startRow; - } - - getRowCount(queryModel, results) { - if (results === null || results === undefined || results.length === 0) { - return null; - } - const currentLastRow = queryModel.startRow + results.length; - return currentLastRow <= queryModel.endRow ? currentLastRow : -1; - } - - cutResultsToPageSize(queryModel, results) { - const pageSize = queryModel.endRow - queryModel.startRow; - if (results && results.length > pageSize) { - return results.splice(0, pageSize); - } else { - return results; - } - } + // ================================================================== }