• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 8432329425

26 Mar 2024 07:31AM CUT coverage: 26.059% (-0.05%) from 26.106%
8432329425

Pull #493

github

web-flow
Merge de535fbae into 98543ea09
Pull Request #493: feat: export csv

2106 of 3377 branches covered (62.36%)

9 of 220 new or added lines in 12 files covered. (4.09%)

1 existing line in 1 file now uncovered.

25609 of 98275 relevant lines covered (26.06%)

5.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts
NEW
1
import { Readable } from 'stream';
×
NEW
2
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
×
NEW
3
import { FieldType } from '@teable/core';
×
NEW
4
import { PrismaService } from '@teable/db-main-prisma';
×
NEW
5
import type { Response } from 'express';
×
NEW
6
import Papa from 'papaparse';
×
NEW
7
import { FieldService } from '../../field/field.service';
×
NEW
8
import { RecordService } from '../../record/record.service';
×
NEW
9

×
NEW
10
@Injectable()
×
NEW
11
export class ExportOpenApiService {
×
NEW
12
  private logger = new Logger(ExportOpenApiService.name);
×
NEW
13
  constructor(
×
NEW
14
    private readonly recordService: RecordService,
×
NEW
15
    private readonly fieldService: FieldService,
×
NEW
16
    private readonly prismaService: PrismaService
×
NEW
17
  ) {}
×
NEW
18
  async exportCsvFromTable(tableId: string, response: Response) {
×
NEW
19
    let count = 0;
×
NEW
20
    let isOver = false;
×
NEW
21
    const csvStream = new Readable({
×
NEW
22
      // eslint-disable-next-line @typescript-eslint/no-empty-function
×
NEW
23
      read() {},
×
NEW
24
    });
×
NEW
25

×
NEW
26
    const tableRaw = await this.prismaService.tableMeta
×
NEW
27
      .findUnique({
×
NEW
28
        where: { id: tableId },
×
NEW
29
        select: { name: true },
×
NEW
30
      })
×
NEW
31
      .catch(() => {
×
NEW
32
        throw new BadRequestException('table is not found');
×
NEW
33
      });
×
NEW
34

×
NEW
35
    response.setHeader('Content-Type', 'text/csv');
×
NEW
36
    response.setHeader(
×
NEW
37
      'Content-Disposition',
×
NEW
38
      `attachment; filename=${tableRaw?.name || 'export'}.csv`
×
NEW
39
    );
×
NEW
40

×
NEW
41
    csvStream.pipe(response);
×
NEW
42

×
NEW
43
    // set headers as first row
×
NEW
44
    const headers = await this.fieldService.getFieldsByQuery(tableId);
×
NEW
45
    const headerData = Papa.unparse([headers.map((h) => h.name)]);
×
NEW
46
    const headersInfoMap = new Map(
×
NEW
47
      headers.map((h, index) => [
×
NEW
48
        h.name,
×
NEW
49
        {
×
NEW
50
          index,
×
NEW
51
          type: h.type,
×
NEW
52
        },
×
NEW
53
      ])
×
NEW
54
    );
×
NEW
55

×
NEW
56
    csvStream.push(headerData);
×
NEW
57

×
NEW
58
    const transformTableToCsvValue = (value: unknown, type: FieldType) => {
×
NEW
59
      let csvValue = value;
×
NEW
60
      if (Array.isArray(value)) {
×
NEW
61
        csvValue =
×
NEW
62
          type === FieldType.Attachment
×
NEW
63
            ? value.map((v) => `${v.name} ${v.presignedUrl}`).join(',')
×
NEW
64
            : value.map((v) => v.title ?? v).join(',');
×
NEW
65
      }
×
NEW
66
      return csvValue;
×
NEW
67
    };
×
NEW
68

×
NEW
69
    try {
×
NEW
70
      while (!isOver) {
×
NEW
71
        const { records } = await this.recordService.getRecords(tableId, {
×
NEW
72
          take: 1000,
×
NEW
73
          skip: count,
×
NEW
74
        });
×
NEW
75
        if (records.length === 0) {
×
NEW
76
          isOver = true;
×
NEW
77
          // end the stream
×
NEW
78
          csvStream.push(null);
×
NEW
79
          break;
×
NEW
80
        }
×
NEW
81

×
NEW
82
        const csvData = Papa.unparse(
×
NEW
83
          records.map((r) => {
×
NEW
84
            const { fields } = r;
×
NEW
85
            const recordsArr = Array.from({ length: headers.length });
×
NEW
86
            for (const [key, value] of Object.entries(fields)) {
×
NEW
87
              const { index: hIndex, type } = headersInfoMap.get(key) ?? {};
×
NEW
88
              if (hIndex !== undefined && type !== undefined) {
×
NEW
89
                recordsArr[hIndex] = transformTableToCsvValue(value, type);
×
NEW
90
              }
×
NEW
91
            }
×
NEW
92
            return recordsArr;
×
NEW
93
          })
×
NEW
94
        );
×
NEW
95
        csvStream.push('\n');
×
NEW
96
        csvStream.push(csvData);
×
NEW
97
        count += records.length;
×
NEW
98
      }
×
NEW
99
    } catch (e) {
×
NEW
100
      csvStream.push('\n');
×
NEW
101
      csvStream.push(`import fail reason:, ${(e as Error)?.message}`);
×
NEW
102
      this.logger.error((e as Error)?.message, `ExportCsv: ${tableId}`);
×
NEW
103
    }
×
NEW
104
  }
×
NEW
105
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc