teableio/teable

View on GitHub
apps/nestjs-backend/src/features/aggregation/open-api/aggregation-open-api.service.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { BadRequestException, Injectable } from '@nestjs/common';
import type { StatisticsFunc } from '@teable/core';
import { getValidStatisticFunc } from '@teable/core';
import type {
  IAggregationRo,
  IAggregationVo,
  IGroupPointsRo,
  IGroupPointsVo,
  IQueryBaseRo,
  IRowCountVo,
} from '@teable/openapi';
import { forIn, isEmpty, map } from 'lodash';
import type { IWithView } from '../aggregation.service';
import { AggregationService } from '../aggregation.service';

@Injectable()
export class AggregationOpenApiService {
  constructor(private readonly aggregationService: AggregationService) {}

  async getAggregation(tableId: string, query?: IAggregationRo): Promise<IAggregationVo> {
    const { viewId, filter: customFilter, field: aggregationFields, groupBy } = query || {};

    let withView: IWithView = { viewId, customFilter, groupBy };

    const fieldStatistics: Array<{ fieldId: string; statisticFunc: StatisticsFunc }> = [];

    forIn(aggregationFields, (value: string[], key) => {
      const fieldStats = map(value, (item) => ({
        fieldId: item,
        statisticFunc: key as StatisticsFunc,
      }));

      fieldStatistics.push(...fieldStats);
    });

    const validFieldStats = await this.validFieldStats(tableId, fieldStatistics);
    if (validFieldStats) {
      withView = { ...withView, customFieldStats: validFieldStats };
    }

    const result = await this.aggregationService.performAggregation({
      tableId: tableId,
      withView,
      search: query?.search,
    });
    return { aggregations: result?.aggregations };
  }

  async getRowCount(tableId: string, query: IQueryBaseRo = {}): Promise<IRowCountVo> {
    const result = await this.aggregationService.performRowCount(tableId, query);
    return {
      rowCount: result.rowCount,
    };
  }

  async getGroupPoints(tableId: string, query?: IGroupPointsRo): Promise<IGroupPointsVo> {
    return await this.aggregationService.getGroupPoints(tableId, query);
  }

  private async validFieldStats(
    tableId: string,
    fieldStatistics: Array<{ fieldId: string; statisticFunc: StatisticsFunc }>
  ) {
    if (isEmpty(fieldStatistics)) {
      return;
    }
    let result: Array<{ fieldId: string; statisticFunc: StatisticsFunc }> | undefined;

    const fieldIds = fieldStatistics.map((item) => item.fieldId);
    const { fieldInstanceMap } = await this.aggregationService.getFieldsData(tableId, fieldIds);

    fieldStatistics.forEach(({ fieldId, statisticFunc }) => {
      const fieldInstance = fieldInstanceMap[fieldId];
      if (!fieldInstance) {
        throw new BadRequestException(`field: '${fieldId}' is invalid`);
      }

      const validStatisticFunc = getValidStatisticFunc(fieldInstance);
      if (!validStatisticFunc.includes(statisticFunc)) {
        throw new BadRequestException(
          `field: '${fieldId}', aggregation func: '${statisticFunc}' is invalid, Only the following func are allowed: [${validStatisticFunc}]`
        );
      }

      (result = result ?? []).push({ fieldId, statisticFunc });
    });
    return result;
  }
}