feat: setup query builder report and create sample report configuration

pull/11/head
Firman Ramdhani 2024-07-03 16:04:40 +07:00
parent c3ffb2b13f
commit 4dc9f7ee99
8 changed files with 255 additions and 150 deletions

View File

@ -0,0 +1,40 @@
import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant';
import { ReportConfigEntity } from '../../../entities/report-config.entity';
export default <ReportConfigEntity>{
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: [],
};

View File

@ -1 +1,5 @@
export const GeneralReportConfig = []; import { ReportConfigEntity } from '../../entities/report-config.entity';
import SampleReport from './configs/sample.report';
export const GeneralReportConfig: ReportConfigEntity[] = [SampleReport];

View File

@ -1,4 +1,8 @@
import { ReportConfigEntity } from '../entities/report-config.entity';
import { GeneralReportConfig } from './general-report'; import { GeneralReportConfig } from './general-report';
import { TenantReportConfig } from './tenant-report'; import { TenantReportConfig } from './tenant-report';
export const ReportConfigs = [...GeneralReportConfig, ...TenantReportConfig]; export const ReportConfigs: ReportConfigEntity[] = [
...GeneralReportConfig,
...TenantReportConfig,
];

View File

@ -0,0 +1,39 @@
import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant';
import { ReportConfigEntity } from '../../../entities/report-config.entity';
export default <ReportConfigEntity>{
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: [],
};

View File

@ -1 +1,4 @@
export const TenantReportConfig = []; import { ReportConfigEntity } from '../../entities/report-config.entity';
import SampleReport from './configs/sample.report';
export const TenantReportConfig: ReportConfigEntity[] = [SampleReport];

View File

@ -1,5 +1,6 @@
export enum REPORT_GROUP { export enum REPORT_GROUP {
// PATTERN => MODULE__MENU__SUB_MENU // PATTERN => MODULE__MENU__SUB_MENU
// EXAMPLE: // EXAMPLE:
contact__reports = 'contact__reports', general_report = 'general_report',
tenant_report = 'tenant_report',
} }

View File

@ -55,5 +55,5 @@ export interface ReportConfigEntity {
filter_configs?: FilterConfigEntity[]; filter_configs?: FilterConfigEntity[];
filter_period_config?: FilterPeriodConfigEntity; filter_period_config?: FilterPeriodConfigEntity;
ignore_filter_keys?: string[]; ignore_filter_keys?: string[];
customQueryFilter?(column: string): string; customQueryColumn?(column: string): string;
} }

View File

@ -8,6 +8,7 @@ import {
export class ReportQueryBuilder { export class ReportQueryBuilder {
public reportConfig: ReportConfigEntity; public reportConfig: ReportConfigEntity;
public queryModel: QueryModelEntity; public queryModel: QueryModelEntity;
constructor(reportConfig: ReportConfigEntity, queryModel: QueryModelEntity) { constructor(reportConfig: ReportConfigEntity, queryModel: QueryModelEntity) {
this.reportConfig = reportConfig; this.reportConfig = reportConfig;
this.queryModel = queryModel; this.queryModel = queryModel;
@ -16,53 +17,73 @@ export class ReportQueryBuilder {
getBaseConfig() { getBaseConfig() {
const tableSchema = this.reportConfig.table_schema; const tableSchema = this.reportConfig.table_schema;
const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; 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 { return {
tableSchema, tableSchema,
mainTableAlias, mainTableAlias,
queryModel, selectSql,
selectSqlExport,
whereSql,
limitSql,
orderBySql,
orderBySqlExport,
...groupBy,
...groupByExport,
}; };
} }
// OLD VERSION getSql(): string {
buildSql(reportConfig: ReportConfigEntity, queryModel: any) { const {
const tableSchema = reportConfig.table_schema; selectSql,
const mainTableAlias = reportConfig.main_table_alias ?? 'main'; tableSchema,
whereSql,
groupByQuery,
orderBySql,
limitSql,
} = this.getBaseConfig();
return `SELECT ${selectSql} FROM ${tableSchema} ${whereSql} ${groupByQuery} ${orderBySql} ${limitSql}` as string;
}
const selectSql = this.createSelectSql(queryModel, reportConfig); getSqlCount(): string {
const selectSqlExport = this.createSelectSqlExport( const { groupByColumn, mainTableAlias, tableSchema, whereSql } =
queryModel, this.getBaseConfig();
reportConfig,
);
const whereSql = this.createWhereSql(queryModel, reportConfig); return `SELECT COUNT(${
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(${
groupByColumn ? `DISTINCT ${groupByColumn}` : `${mainTableAlias}.id` groupByColumn ? `DISTINCT ${groupByColumn}` : `${mainTableAlias}.id`
}) ${ }) ${
groupByColumn groupByColumn
? `+ COUNT(DISTINCT CASE WHEN ${groupByColumn} IS NULL THEN 1 END)` ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumn} IS NULL THEN 1 END)`
: '' : ''
} AS count FROM ${tableSchema} ${whereSql}` as string; } 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 groupByColumnExport
? `DISTINCT ${groupByColumnExport}` ? `DISTINCT ${groupByColumnExport}`
: `${mainTableAlias}.id` : `${mainTableAlias}.id`
@ -71,17 +92,70 @@ export class ReportQueryBuilder {
? `+ COUNT(DISTINCT CASE WHEN ${groupByColumnExport} IS NULL THEN 1 END)` ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumnExport} IS NULL THEN 1 END)`
: '' : ''
} AS count FROM ${tableSchema} ${whereSql}` as string; } AS count FROM ${tableSchema} ${whereSql}` as string;
return { SQL, SQL_COUNT, SQL_EXPORT, SQL_EXPORT_COUNT };
} }
createSelectSql(queryModel: any, reportConfig: ReportConfigEntity): string { isDoingGrouping(rowGroupCols, groupKeys) {
const configColumns = reportConfig.column_configs; // we are not doing grouping if at the lowest level. we are at the lowest level
const mainTableAlias = reportConfig.main_table_alias ?? 'main'; // 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; interpolate(str, o) {
const valueCols = queryModel.valueCols; return str.replace(/{([^{}]*)}/g, function (a, b) {
const groupKeys = queryModel.groupKeys; 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)) { if (this.isDoingGrouping(rowGroupCols, groupKeys)) {
const colsToSelect = []; const colsToSelect = [];
@ -89,14 +163,12 @@ export class ReportQueryBuilder {
const rowGroupColChildCount = rowGroupCols[groupKeys.length + 1]; const rowGroupColChildCount = rowGroupCols[groupKeys.length + 1];
colsToSelect.push( colsToSelect.push(
this.findQueryConfig(reportConfig, rowGroupCol.field) + this.findQueryConfig(rowGroupCol.field) + ` AS ${rowGroupCol.field}`,
` AS ${rowGroupCol.field}`,
); );
if (rowGroupColChildCount) { if (rowGroupColChildCount) {
colsToSelect.push( colsToSelect.push(
`COUNT(DISTINCT ${this.findQueryConfig( `COUNT(DISTINCT ${this.findQueryConfig(
reportConfig,
rowGroupColChildCount.field, rowGroupColChildCount.field,
)}) AS count_child_group`, )}) AS count_child_group`,
); );
@ -109,7 +181,6 @@ export class ReportQueryBuilder {
valueCols.forEach(function (valueCol) { valueCols.forEach(function (valueCol) {
colsToSelect.push( colsToSelect.push(
`${valueCol.aggFunc} (${thisSelf.findQueryConfig( `${valueCol.aggFunc} (${thisSelf.findQueryConfig(
reportConfig,
valueCol.field, valueCol.field,
)}) AS ${valueCol.field}`, )}) AS ${valueCol.field}`,
); );
@ -124,14 +195,11 @@ export class ReportQueryBuilder {
return columns.join(', '); return columns.join(', ');
} }
createSelectSqlExport( createSelectSqlExport(): string {
queryModel: any, const configColumns = this.reportConfig.column_configs;
reportConfig: ReportConfigEntity,
): string {
const configColumns = reportConfig.column_configs;
const rowGroupCols = queryModel.rowGroupCols; const rowGroupCols = this.queryModel.rowGroupCols;
const valueCols = queryModel.valueCols; const valueCols = this.queryModel.valueCols;
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
const thisSelf = this; const thisSelf = this;
@ -140,7 +208,7 @@ export class ReportQueryBuilder {
const colsToSelect = []; const colsToSelect = [];
rowGroupCols.forEach(function (rowGroupCol) { rowGroupCols.forEach(function (rowGroupCol) {
colsToSelect.push( colsToSelect.push(
thisSelf.findQueryConfig(reportConfig, rowGroupCol.field) + thisSelf.findQueryConfig(rowGroupCol.field) +
` AS ${rowGroupCol.field}`, ` AS ${rowGroupCol.field}`,
); );
}); });
@ -148,7 +216,6 @@ export class ReportQueryBuilder {
valueCols.forEach(function (valueCol) { valueCols.forEach(function (valueCol) {
colsToSelect.push( colsToSelect.push(
`${valueCol.aggFunc} (${thisSelf.findQueryConfig( `${valueCol.aggFunc} (${thisSelf.findQueryConfig(
reportConfig,
valueCol.field, valueCol.field,
)}) AS ${valueCol.field}`, )}) AS ${valueCol.field}`,
); );
@ -161,7 +228,9 @@ export class ReportQueryBuilder {
return columns.join(', '); return columns.join(', ');
} }
// ==================================================================
// GENERATE WHERE QUERY =============================================
createFilterSql(column: string, item: { type: FILTER_TYPE; filter: any }) { createFilterSql(column: string, item: { type: FILTER_TYPE; filter: any }) {
switch (item.type) { switch (item.type) {
// TEXT // TEXT
@ -237,23 +306,22 @@ export class ReportQueryBuilder {
} }
} }
createWhereSql(queryModel, reportConfig: ReportConfigEntity) { createWhereSql() {
// const configColumns = reportConfig.column_configs ?? []; const configFilters = this.reportConfig.filter_configs ?? [];
const configFilters = reportConfig.filter_configs ?? []; const ignoreFilterKeys = this.reportConfig.ignore_filter_keys ?? [];
const ignoreFilterKeys = reportConfig.ignore_filter_keys ?? [];
const ignoreFilter = configFilters const ignoreFilter = configFilters
.filter((el) => el.hide_field) .filter((el) => el.hide_field)
.map((el) => el.filter_column); .map((el) => el.filter_column);
const ignoreFilterKey = [...ignoreFilter, ...ignoreFilterKeys]; const ignoreFilterKey = [...ignoreFilter, ...ignoreFilterKeys];
const whereCondition = reportConfig?.whereCondition const whereCondition = this.reportConfig?.whereCondition
? reportConfig.whereCondition(queryModel.filterModel) ? this.reportConfig.whereCondition(this.queryModel.filterModel)
: []; : [];
const rowGroupCols = queryModel.rowGroupCols; const rowGroupCols = this.queryModel.rowGroupCols;
const groupKeys = queryModel.groupKeys; const groupKeys = this.queryModel.groupKeys;
const filterModel = queryModel.filterModel; const filterModel = this.queryModel.filterModel;
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
const thisSelf = this; const thisSelf = this;
@ -263,9 +331,7 @@ export class ReportQueryBuilder {
groupKeys.forEach(function (key, index) { groupKeys.forEach(function (key, index) {
const colName = rowGroupCols[index].field; const colName = rowGroupCols[index].field;
// whereParts.push(colName + ' = "' + key + '"'); // whereParts.push(colName + ' = "' + key + '"');
whereParts.push( whereParts.push(`${thisSelf.findQueryConfig(colName)} = '${key}'`);
`${thisSelf.findQueryConfig(reportConfig, colName)} = '${key}'`,
);
}); });
} }
@ -274,14 +340,14 @@ export class ReportQueryBuilder {
keySet.forEach(function (key) { keySet.forEach(function (key) {
if (!ignoreFilterKey.includes(key)) { if (!ignoreFilterKey.includes(key)) {
const item = filterModel[key]; const item = filterModel[key];
const newKey = thisSelf.findQueryConfig(reportConfig, key); const newKey = thisSelf.findQueryConfig(key);
whereParts.push(thisSelf.createFilterSql(newKey, item)); whereParts.push(thisSelf.createFilterSql(newKey, item));
} }
}); });
} }
// set default where conditions // set default where conditions
const defaultConditions = reportConfig.whereDefaultConditions; const defaultConditions = this.reportConfig.whereDefaultConditions;
const defaultWhereOptions = []; const defaultWhereOptions = [];
if (defaultConditions) { if (defaultConditions) {
defaultConditions.forEach((condition) => { defaultConditions.forEach((condition) => {
@ -309,15 +375,17 @@ export class ReportQueryBuilder {
return ''; return '';
} }
} }
// ==================================================================
createOrderBySql(queryModel, reportConfig: ReportConfigEntity) { // GENERATE ORDER QUERY =============================================
const mainTableAlias = reportConfig.main_table_alias ?? 'main'; createOrderBySql() {
const defaultOrderBy = reportConfig.defaultOrderBy ?? []; const mainTableAlias = this.reportConfig.main_table_alias ?? 'main';
const lowLevelOrderBy = reportConfig.lowLevelOrderBy ?? []; const defaultOrderBy = this.reportConfig.defaultOrderBy ?? [];
const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? [];
const rowGroupCols = queryModel.rowGroupCols; const rowGroupCols = this.queryModel.rowGroupCols;
const groupKeys = queryModel.groupKeys; const groupKeys = this.queryModel.groupKeys;
const sortModel = queryModel.sortModel; const sortModel = this.queryModel.sortModel;
const grouping = this.isDoingGrouping(rowGroupCols, groupKeys); const grouping = this.isDoingGrouping(rowGroupCols, groupKeys);
@ -370,13 +438,13 @@ export class ReportQueryBuilder {
} }
} }
createOrderBySqlExport(queryModel, reportConfig: ReportConfigEntity) { createOrderBySqlExport() {
const mainTableAlias = reportConfig.main_table_alias ?? 'main'; const mainTableAlias = this.reportConfig.main_table_alias ?? 'main';
const defaultOrderBy = reportConfig.defaultOrderBy ?? []; const defaultOrderBy = this.reportConfig.defaultOrderBy ?? [];
const lowLevelOrderBy = reportConfig.lowLevelOrderBy ?? []; const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? [];
const rowGroupCols = queryModel.rowGroupCols; const rowGroupCols = this.queryModel.rowGroupCols;
const sortModel = queryModel.sortModel; const sortModel = this.queryModel.sortModel;
const defaultOrder = defaultOrderBy[0] const defaultOrder = defaultOrderBy[0]
? defaultOrderBy ? defaultOrderBy
@ -400,17 +468,18 @@ export class ReportQueryBuilder {
); );
} }
} }
// ==================================================================
createGroupBySql(queryModel, reportConfig: ReportConfigEntity) { // GENERATE GROUP BY QUERY ==========================================
// const configColumns = reportConfig.column_configs; createGroupBySql() {
const rowGroupCols = queryModel.rowGroupCols; const rowGroupCols = this.queryModel.rowGroupCols;
const groupKeys = queryModel.groupKeys; const groupKeys = this.queryModel.groupKeys;
if (this.isDoingGrouping(rowGroupCols, groupKeys)) { if (this.isDoingGrouping(rowGroupCols, groupKeys)) {
const colsToGroupBy = []; const colsToGroupBy = [];
const rowGroupCol = rowGroupCols[groupKeys.length]; const rowGroupCol = rowGroupCols[groupKeys.length];
colsToGroupBy.push(this.findQueryConfig(reportConfig, rowGroupCol.field)); colsToGroupBy.push(this.findQueryConfig(rowGroupCol.field));
return { return {
groupByQuery: ' GROUP BY ' + colsToGroupBy.join(', '), groupByQuery: ' GROUP BY ' + colsToGroupBy.join(', '),
@ -424,9 +493,8 @@ export class ReportQueryBuilder {
} }
} }
createGroupBySqlExport(queryModel, reportConfig: ReportConfigEntity) { createGroupBySqlExport() {
// const configColumns = reportConfig.column_configs; const rowGroupCols = this.queryModel.rowGroupCols;
const rowGroupCols = queryModel.rowGroupCols;
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
const thisSelf = this; const thisSelf = this;
@ -434,9 +502,7 @@ export class ReportQueryBuilder {
if (rowGroupCols.length > 0) { if (rowGroupCols.length > 0) {
const colsToGroupBy = []; const colsToGroupBy = [];
rowGroupCols.forEach(function (rowGroupCol) { rowGroupCols.forEach(function (rowGroupCol) {
colsToGroupBy.push( colsToGroupBy.push(thisSelf.findQueryConfig(rowGroupCol.field));
thisSelf.findQueryConfig(reportConfig, rowGroupCol.field),
);
}); });
return { 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;
}
}
} }