teableio/teable

View on GitHub
apps/nestjs-backend/test/utils/record-mock.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { faker } from '@faker-js/faker';
import type { Field } from '@prisma/client';
import { PrismaClient } from '@prisma/client';
import type { IRatingFieldOptions, ISelectFieldOptions } from '@teable/core';
import { parseDsn, IdPrefix, Colors, FieldType, generateRecordId } from '@teable/core';
import * as dotenv from 'dotenv-flow';
import Knex from 'knex';
import { chunk, flatten, groupBy } from 'lodash';

dotenv.config({ path: '../../../nextjs-app', default_node_env: 'development' });

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion

async function rectifyField(
  prisma: PrismaClient,
  fields: Field[],
  selectOptions: ISelectFieldOptions
) {
  const fieldByType = groupBy(fields, 'type');

  const rectifySelectField = [
    ...(fieldByType?.['singleSelect'] || []),
    ...(fieldByType?.['multipleSelect'] || []),
  ]
    .filter((value) => value)
    .map((value) => value.id);

  if (rectifySelectField) {
    await prisma.field.updateMany({
      where: { id: { in: rectifySelectField } },
      data: {
        options: JSON.stringify(selectOptions),
      },
    });
  }
}

async function generateFieldData(params: {
  mockDataNum: number;
  fields: Field[];
  selectOptions: ISelectFieldOptions;
}) {
  const { fields, selectOptions, mockDataNum } = params;

  return fields.reduce<{ [dbFieldName: string]: unknown }>((pre, cur) => {
    const selectArray = selectOptions.choices.map((value) => value.name);

    let fieldData: unknown = undefined;
    switch (cur.type as FieldType) {
      case FieldType.SingleLineText:
      case FieldType.LongText: {
        fieldData = faker.internet.userName();
        break;
      }
      case FieldType.Number: {
        fieldData = faker.number.float({ min: 1, max: mockDataNum });
        break;
      }
      case FieldType.SingleSelect: {
        fieldData = faker.helpers.arrayElement(selectArray);
        break;
      }
      case FieldType.MultipleSelect: {
        fieldData = JSON.stringify(faker.helpers.arrayElements(selectArray, { min: 2, max: 9 }));
        break;
      }
      case FieldType.Checkbox: {
        fieldData = faker.helpers.arrayElement([1, 'null']);
        break;
      }
      case FieldType.Date: {
        fieldData = faker.date.anytime().toISOString();
        break;
      }
      case FieldType.Rating: {
        const ratingFieldOptions = JSON.parse(cur.options!) as IRatingFieldOptions;
        fieldData = faker.number.int({ min: 0, max: ratingFieldOptions.max });
        break;
      }
    }

    (fieldData || fieldData === 0) && (pre[cur.dbFieldName] = fieldData);
    return pre;
  }, {});
}

export async function seeding(tableId: string, mockDataNum: number) {
  const databaseUrl = process.env.PRISMA_DATABASE_URL!;
  console.log('database-url: ', databaseUrl);
  const { driver } = parseDsn(databaseUrl);
  console.log('driver: ', driver);
  const prisma = new PrismaClient();

  console.log(`Start seeding ...`);

  const selectOptions: ISelectFieldOptions = {
    choices: [
      { id: 'chobird', name: 'bird', color: Colors.GreenDark1 },
      { id: 'chofish', name: 'fish', color: Colors.PurpleLight2 },
      { id: 'cholion', name: 'lion', color: Colors.OrangeLight1 },
      { id: 'choelephant', name: 'elephant', color: Colors.CyanLight2 },
      { id: 'chotiger', name: 'tiger', color: Colors.Yellow },
      { id: 'chorabbit', name: 'rabbit', color: Colors.Red },
      { id: 'chobear', name: 'bear', color: Colors.YellowLight1 },
      { id: 'chohorse', name: 'horse', color: Colors.RedBright },
      { id: 'chosnake', name: 'snake', color: Colors.RedLight2 },
      { id: 'chomonkey', name: 'monkey', color: Colors.Gray },
    ],
  };

  const fields = await prisma.field.findMany({
    where: {
      tableId,
      deletedTime: null,
    },
  });

  await rectifyField(prisma, fields, selectOptions);

  const { dbTableName, name: tableName } = await prisma.tableMeta.findUniqueOrThrow({
    select: { dbTableName: true, name: true },
    where: { id: tableId },
  });
  console.log(`Table: ${tableName}, mockDataNum: ${mockDataNum}`);

  const knex = Knex({
    client: driver,
  });

  console.time(`Table: ${tableName}, Ready Install Data`);
  const data: { [dbFieldName: string]: unknown }[] = [];
  for (let i = 0; i < mockDataNum; i++) {
    const fieldData = await generateFieldData({ mockDataNum, fields, selectOptions });

    data.push({
      __id: generateRecordId(),
      __created_time: new Date().toISOString(),
      __created_by: 'admin',
      __last_modified_by: 'admin',
      __version: 1,
      ...fieldData,
    });
  }
  console.timeEnd(`Table: ${tableName}, Ready Install Data`);

  console.time(`Table: ${tableName}, Install Data Num: ${mockDataNum}`);
  const pages = chunk(data, 50000);

  const promises = pages.map((page) => {
    const sql = `
        INSERT INTO ${knex.ref(dbTableName)}
        ("${Object.keys(page[0]).join('", "')}")
        VALUES
        ${page
          .map((d) => `('${Object.values(d).join(`', '`)}')`)
          .join(', ')
          .replace(/'null'/g, 'null')} 
      `;

    const sqlOp = `
        INSERT INTO ops
        ("collection", "doc_id", "doc_type", "version", "operation", "created_by")
        VALUES
        ${page
          .map((d) => {
            return {
              collection: tableId,
              doc_id: d.__id,
              doc_type: IdPrefix.Record,
              version: 0,
              operation: '{}',
              created_by: 'mock',
            };
          })
          .map((d) => `('${Object.values(d).join(`', '`)}')`)
          .join(', ')}
      `;

    return [prisma.$executeRawUnsafe(sql), prisma.$executeRawUnsafe(sqlOp)];
  });

  await prisma.$transaction(flatten(promises));
  console.timeEnd(`Table: ${tableName}, Install Data Num: ${mockDataNum}`);
  return tableId;
}