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

teableio / teable / 8370273405

21 Mar 2024 05:44AM CUT coverage: 28.222% (-0.009%) from 28.231%
8370273405

push

github

web-flow
fix: drag row order not work (#482)

2122 of 3238 branches covered (65.53%)

0 of 33 new or added lines in 3 files covered. (0.0%)

16 existing lines in 1 file now uncovered.

25811 of 91456 relevant lines covered (28.22%)

5.57 hits per line

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

45.64
/apps/nestjs-backend/src/share-db/share-db.service.ts
1
import { Injectable, Logger } from '@nestjs/common';
1✔
2
import { loadPackage } from '@nestjs/common/utils/load-package.util';
1✔
3
import { context as otelContext, trace as otelTrace } from '@opentelemetry/api';
1✔
4
import { FieldOpBuilder, IdPrefix, ViewOpBuilder } from '@teable/core';
1✔
5
import { PrismaService } from '@teable/db-main-prisma';
1✔
6
import { noop } from 'lodash';
1✔
7
import { ClsService } from 'nestjs-cls';
1✔
8
import type { CreateOp, DeleteOp, EditOp } from 'sharedb';
1✔
9
import ShareDBClass from 'sharedb';
1✔
10
import { CacheConfig, ICacheConfig } from '../configs/cache.config';
1✔
11
import { EventEmitterService } from '../event-emitter/event-emitter.service';
1✔
12
import type { IClsStore } from '../types/cls';
1✔
13
import { Timing } from '../utils/timing';
1✔
14
import { authMiddleware } from './auth.middleware';
1✔
15
import { derivateMiddleware } from './derivate.middleware';
1✔
16
import type { IRawOpMap } from './interface';
1✔
17
import { ShareDbPermissionService } from './share-db-permission.service';
1✔
18
import { ShareDbAdapter } from './share-db.adapter';
1✔
19
import { WsDerivateService } from './ws-derivate.service';
1✔
20

1✔
21
@Injectable()
1✔
22
export class ShareDbService extends ShareDBClass {
1✔
23
  private logger = new Logger(ShareDbService.name);
150✔
24

150✔
25
  constructor(
150✔
26
    readonly shareDbAdapter: ShareDbAdapter,
150✔
27
    private readonly eventEmitterService: EventEmitterService,
150✔
28
    private readonly prismaService: PrismaService,
150✔
29
    private readonly cls: ClsService<IClsStore>,
150✔
30
    private readonly wsDerivateService: WsDerivateService,
150✔
31
    private readonly shareDbPermissionService: ShareDbPermissionService,
150✔
32
    @CacheConfig() private readonly cacheConfig: ICacheConfig
150✔
33
  ) {
150✔
34
    super({
150✔
35
      presence: true,
150✔
36
      doNotForwardSendPresenceErrorsToClient: true,
150✔
37
      db: shareDbAdapter,
150✔
38
    });
150✔
39

150✔
40
    const { provider, redis } = this.cacheConfig;
150✔
41

150✔
42
    if (provider === 'redis') {
150!
43
      const redisPubsub = loadPackage('sharedb-redis-pubsub', ShareDbService.name, () =>
×
44
        require('sharedb-redis-pubsub')
×
45
      )({ url: redis.uri });
×
46

×
47
      this.logger.log(`> Detected Redis cache; enabled the Redis pub/sub adapter for ShareDB.`);
×
48
      this.pubsub = redisPubsub;
×
49
    }
×
50

150✔
51
    // auth
150✔
52
    authMiddleware(this, this.shareDbPermissionService);
150✔
53
    derivateMiddleware(this, this.cls, this.wsDerivateService);
150✔
54

150✔
55
    this.use('submit', this.onSubmit);
150✔
56

150✔
57
    // broadcast raw op events to client
150✔
58
    this.prismaService.bindAfterTransaction(() => {
150✔
59
      const rawOpMaps = this.cls.get('tx.rawOpMaps');
×
60
      const stashOpMap = this.cls.get('tx.stashOpMap');
×
61
      this.cls.set('tx.rawOpMaps', undefined);
×
62
      this.cls.set('tx.stashOpMap', undefined);
×
63

×
64
      const ops: IRawOpMap[] = [];
×
65
      if (stashOpMap) {
×
66
        ops.push(stashOpMap);
×
67
      }
×
68
      if (rawOpMaps?.length) {
×
69
        ops.push(...rawOpMaps);
×
70
      }
×
71

×
72
      if (ops.length) {
×
73
        this.publishOpsMap(rawOpMaps);
×
74
        this.eventEmitterService.ops2Event(ops);
×
75
      }
×
76
    });
×
77
  }
150✔
78

150✔
79
  getConnection() {
150✔
80
    const connection = this.connect();
×
81
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
×
82
    connection.agent!.custom.isBackend = true;
×
83
    return connection;
×
84
  }
×
85

150✔
86
  @Timing()
150✔
87
  publishOpsMap(rawOpMaps: IRawOpMap[] | undefined) {
150✔
88
    if (!rawOpMaps?.length) {
×
89
      return;
×
90
    }
×
91
    for (const rawOpMap of rawOpMaps) {
×
92
      for (const collection in rawOpMap) {
×
93
        const data = rawOpMap[collection];
×
94
        for (const docId in data) {
×
95
          const rawOp = data[docId] as EditOp | CreateOp | DeleteOp;
×
96
          const channels = [collection, `${collection}.${docId}`];
×
97
          rawOp.c = collection;
×
98
          rawOp.d = docId;
×
99
          this.pubsub.publish(channels, rawOp, noop);
×
100

×
101
          if (this.shouldPublishAction(rawOp)) {
×
102
            const tableId = collection.split('_')[1];
×
103
            this.publishRelatedChannels(tableId, rawOp);
×
104
          }
×
105
        }
×
106
      }
×
107
    }
×
108
  }
×
109

150✔
110
  private shouldPublishAction(rawOp: EditOp | CreateOp | DeleteOp) {
150✔
NEW
111
    const viewKeys = ['filter', 'sort', 'group', 'lastModifiedTime'];
×
112
    const fieldKeys = ['options'];
×
113
    return rawOp.op?.some(
×
114
      (op) =>
×
115
        viewKeys.includes(ViewOpBuilder.editor.setViewProperty.detect(op)?.key as string) ||
×
116
        fieldKeys.includes(FieldOpBuilder.editor.setFieldProperty.detect(op)?.key as string)
×
117
    );
×
118
  }
×
119

150✔
120
  /**
150✔
121
   * this is for some special scenarios like manual sort
150✔
122
   * which only send view ops but update record too
150✔
123
   */
150✔
124
  private publishRelatedChannels(tableId: string, rawOp: EditOp | CreateOp | DeleteOp) {
150✔
125
    this.pubsub.publish([`${IdPrefix.Record}_${tableId}`], rawOp, noop);
×
126
    this.pubsub.publish([`${IdPrefix.Field}_${tableId}`], rawOp, noop);
×
127
  }
×
128

150✔
129
  private onSubmit = (
150✔
130
    context: ShareDBClass.middleware.SubmitContext,
×
131
    next: (err?: unknown) => void
×
132
  ) => {
×
133
    const tracer = otelTrace.getTracer('default');
×
134
    const currentSpan = tracer.startSpan('submitOp');
×
135

×
136
    // console.log('onSubmit start');
×
137

×
138
    otelContext.with(otelTrace.setSpan(otelContext.active(), currentSpan), () => {
×
139
      const [docType] = context.collection.split('_');
×
140

×
141
      if (docType !== IdPrefix.Record || !context.op.op) {
×
142
        return next(new Error('only record op can be committed'));
×
143
      }
×
144
      next();
×
145
    });
×
146

×
147
    // console.log('onSubmit end');
×
148
  };
×
149
}
150✔
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