Skip to content

Commit

Permalink
fix: dashboard data api (#141)
Browse files Browse the repository at this point in the history
* fix: update count timezone bug

* fix: update statistics data real time
  • Loading branch information
h4l-yup committed Jan 23, 2024
1 parent da8b4b1 commit b304947
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 38 deletions.
41 changes: 34 additions & 7 deletions apps/api/src/domains/feedback/feedback.mysql.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ import { ChannelEntity } from '../channel/channel/channel.entity';
import type { FieldEntity } from '../channel/field/field.entity';
import { OptionEntity } from '../channel/option/option.entity';
import { IssueEntity } from '../project/issue/issue.entity';
import type {
CountByProjectIdDto,
DeleteByIdsDto,
FindFeedbacksByChannelIdDto,
} from './dtos';
import { FeedbackIssueStatisticsService } from '../statistics/feedback-issue/feedback-issue-statistics.service';
import { FeedbackStatisticsService } from '../statistics/feedback/feedback-statistics.service';
import type { CountByProjectIdDto, FindFeedbacksByChannelIdDto } from './dtos';
import {
AddIssueDto,
CreateFeedbackMySQLDto,
DeleteByIdsDto,
RemoveIssueDto,
UpdateFeedbackMySQLDto,
} from './dtos';
Expand All @@ -54,6 +53,8 @@ export class FeedbackMySQLService {
@InjectRepository(OptionEntity)
private readonly optionRepository: Repository<OptionEntity>,
private readonly cls: ClsService<ClsServiceType>,
private readonly feedbackStatisticsService: FeedbackStatisticsService,
private readonly feedbackIssueStatisticsService: FeedbackIssueStatisticsService,
) {}

@Transactional()
Expand All @@ -62,6 +63,13 @@ export class FeedbackMySQLService {
feedback.channel = new ChannelEntity();
feedback.channel.id = channelId;
feedback.rawData = data;

await this.feedbackStatisticsService.updateCount({
channelId,
date: DateTime.utc().toJSDate(),
count: 1,
});

return await this.feedbackRepository.save(feedback);
}

Expand Down Expand Up @@ -326,6 +334,12 @@ export class FeedbackMySQLService {
feedbackCount: () => 'feedback_count + 1',
updatedAt: () => `'${DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss')}'`,
});

await this.feedbackIssueStatisticsService.updateFeedbackCount({
issueId: dto.issueId,
date: feedback.createdAt,
feedbackCount: 1,
});
} catch (e) {
if (e instanceof QueryFailedError) {
if (e.driverError.code === 'ER_NO_REFERENCED_ROW_2') {
Expand Down Expand Up @@ -363,6 +377,12 @@ export class FeedbackMySQLService {
feedbackCount: () => 'feedback_count - 1',
updatedAt: () => `'${DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss')}'`,
});

await this.feedbackIssueStatisticsService.updateFeedbackCount({
issueId,
date: feedback.createdAt,
feedbackCount: -1,
});
} catch (e) {
if (e instanceof QueryFailedError) {
this.logger.error(e);
Expand All @@ -379,10 +399,11 @@ export class FeedbackMySQLService {
});
}

async deleteByIds({ feedbackIds }: DeleteByIdsDto) {
@Transactional()
async deleteByIds({ channelId, feedbackIds }: DeleteByIdsDto) {
const feedbacks = await this.feedbackRepository.find({
where: { id: In(feedbackIds) },
relations: ['issues'],
relations: { issues: true },
});

for (const feedback of feedbacks) {
Expand All @@ -393,6 +414,12 @@ export class FeedbackMySQLService {
`'${DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss')}'`,
});
}

await this.feedbackStatisticsService.updateCount({
channelId,
date: feedback.createdAt,
count: -1,
});
}

await this.feedbackRepository.remove(feedbacks);
Expand Down
44 changes: 44 additions & 0 deletions apps/api/src/domains/feedback/feedback.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { ChannelEntity } from '../channel/channel/channel.entity';
import { RESERVED_FIELD_KEYS } from '../channel/field/field.constants';
import { FieldEntity } from '../channel/field/field.entity';
import { IssueEntity } from '../project/issue/issue.entity';
import { ProjectEntity } from '../project/project/project.entity';
import { FeedbackIssueStatisticsEntity } from '../statistics/feedback-issue/feedback-issue-statistics.entity';
import { FeedbackStatisticsEntity } from '../statistics/feedback/feedback-statistics.entity';
import { IssueStatisticsEntity } from '../statistics/issue/issue-statistics.entity';
import { CreateFeedbackDto, FindFeedbacksByChannelIdDto } from './dtos';
Expand Down Expand Up @@ -72,9 +74,11 @@ describe('FeedbackService Test Suite', () => {
let fieldRepo: Repository<FieldEntity>;
let issueRepo: Repository<IssueEntity>;
let channelRepo: Repository<ChannelEntity>;
let projectRepo: Repository<ProjectEntity>;
let osRepo: OpensearchRepository;
let feedbackStatsRepo: Repository<FeedbackStatisticsEntity>;
let issueStatsRepo: Repository<IssueStatisticsEntity>;
let feedbackIssueStatsRepo: Repository<FeedbackIssueStatisticsEntity>;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [TestConfig],
Expand All @@ -87,18 +91,28 @@ describe('FeedbackService Test Suite', () => {
fieldRepo = module.get(getRepositoryToken(FieldEntity));
issueRepo = module.get(getRepositoryToken(IssueEntity));
channelRepo = module.get(getRepositoryToken(ChannelEntity));
projectRepo = module.get(getRepositoryToken(ProjectEntity));
osRepo = module.get(OpensearchRepository);
feedbackStatsRepo = module.get(
getRepositoryToken(FeedbackStatisticsEntity),
);
issueStatsRepo = module.get(getRepositoryToken(IssueStatisticsEntity));
feedbackIssueStatsRepo = module.get(
getRepositoryToken(FeedbackIssueStatisticsEntity),
);
});

describe('create', () => {
it('creating a feedback succeeds with valid inputs', async () => {
const dto = new CreateFeedbackDto();
dto.channelId = faker.number.int();
dto.data = JSON.parse(JSON.stringify(feedbackFixture));
jest.spyOn(projectRepo, 'findOne').mockResolvedValue({
id: faker.number.int(),
timezone: {
offset: '+09:00',
},
} as ProjectEntity);
jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture);
jest.spyOn(feedbackRepo, 'save').mockResolvedValue({
id: faker.number.int(),
Expand Down Expand Up @@ -312,6 +326,12 @@ describe('FeedbackService Test Suite', () => {
}).map(() => faker.string.sample());
dto.data.issueNames = [...issueNames, faker.string.sample()];
const feedbackId = faker.number.int();
jest.spyOn(projectRepo, 'findOne').mockResolvedValue({
id: faker.number.int(),
timezone: {
offset: '+09:00',
},
} as ProjectEntity);
jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture);
jest.spyOn(feedbackRepo, 'save').mockResolvedValue({
id: feedbackId,
Expand All @@ -338,13 +358,17 @@ describe('FeedbackService Test Suite', () => {
jest.spyOn(feedbackRepo, 'findOne').mockResolvedValue({
id: feedbackId,
issues: [],
createdAt: new Date(),
} as FeedbackEntity);
jest
.spyOn(feedbackStatsRepo, 'findOne')
.mockResolvedValue({ count: 1 } as FeedbackStatisticsEntity);
jest
.spyOn(issueStatsRepo, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
jest
.spyOn(feedbackIssueStatsRepo, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
clsService.set = jest.fn();

await feedbackService.create(dto);
Expand All @@ -365,6 +389,12 @@ describe('FeedbackService Test Suite', () => {
}).map(() => faker.string.sample());
dto.data.issueNames = [...issueNames];
const feedbackId = faker.number.int();
jest.spyOn(projectRepo, 'findOne').mockResolvedValue({
id: faker.number.int(),
timezone: {
offset: '+09:00',
},
} as ProjectEntity);
jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture);
jest.spyOn(feedbackRepo, 'save').mockResolvedValue({
id: feedbackId,
Expand All @@ -378,10 +408,14 @@ describe('FeedbackService Test Suite', () => {
jest.spyOn(feedbackRepo, 'findOne').mockResolvedValue({
id: feedbackId,
issues: [],
createdAt: new Date(),
} as FeedbackEntity);
jest
.spyOn(feedbackStatsRepo, 'findOne')
.mockResolvedValue({ count: 1 } as FeedbackStatisticsEntity);
jest
.spyOn(feedbackIssueStatsRepo, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
clsService.set = jest.fn();

await feedbackService.create(dto);
Expand All @@ -397,6 +431,12 @@ describe('FeedbackService Test Suite', () => {
dto.data = JSON.parse(JSON.stringify(feedbackFixture));
dto.data.issueNames = [faker.string.sample()];
const feedbackId = faker.number.int();
jest.spyOn(projectRepo, 'findOne').mockResolvedValue({
id: faker.number.int(),
timezone: {
offset: '+09:00',
},
} as ProjectEntity);
jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture);
jest.spyOn(feedbackRepo, 'save').mockResolvedValue({
id: feedbackId,
Expand All @@ -420,13 +460,17 @@ describe('FeedbackService Test Suite', () => {
jest.spyOn(feedbackRepo, 'findOne').mockResolvedValue({
id: feedbackId,
issues: [],
createdAt: new Date(),
} as FeedbackEntity);
jest
.spyOn(feedbackStatsRepo, 'findOne')
.mockResolvedValue({ count: 1 } as FeedbackStatisticsEntity);
jest
.spyOn(issueStatsRepo, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
jest
.spyOn(feedbackIssueStatsRepo, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
clsService.set = jest.fn();

await feedbackService.create(dto);
Expand Down
16 changes: 0 additions & 16 deletions apps/api/src/domains/feedback/feedback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ import type { FieldEntity } from '../channel/field/field.entity';
import { FieldService } from '../channel/field/field.service';
import { OptionService } from '../channel/option/option.service';
import { IssueService } from '../project/issue/issue.service';
import { FeedbackIssueStatisticsService } from '../statistics/feedback-issue/feedback-issue-statistics.service';
import { FeedbackStatisticsService } from '../statistics/feedback/feedback-statistics.service';
import type {
CountByProjectIdDto,
CreateImageUploadUrlDto,
Expand Down Expand Up @@ -72,8 +70,6 @@ export class FeedbackService {
private readonly optionService: OptionService,
private readonly channelService: ChannelService,
private readonly configService: ConfigService,
private readonly feedbackStatisticsService: FeedbackStatisticsService,
private readonly feedbackIssueStatisticsService: FeedbackIssueStatisticsService,
) {}

private validateQuery(
Expand Down Expand Up @@ -369,12 +365,6 @@ export class FeedbackService {
data: feedbackData,
});

await this.feedbackStatisticsService.updateCount({
channelId,
date: DateTime.utc().toJSDate(),
count: 1,
});

if (issueNames) {
for (const issueName of issueNames) {
let issue = await this.issueService.findByName({ name: issueName });
Expand Down Expand Up @@ -510,12 +500,6 @@ export class FeedbackService {
async addIssue(dto: AddIssueDto) {
await this.feedbackMySQLService.addIssue(dto);

await this.feedbackIssueStatisticsService.updateFeedbackCount({
issueId: dto.issueId,
date: DateTime.utc().toJSDate(),
feedbackCount: 1,
});

if (this.configService.get('opensearch.use')) {
await this.feedbackOSService.upsertFeedbackItem({
channelId: dto.channelId,
Expand Down
9 changes: 9 additions & 0 deletions apps/api/src/domains/project/issue/issue.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { TimeRange } from '@/common/dtos';
import { IssueStatisticsEntity } from '@/domains/statistics/issue/issue-statistics.entity';
import { createQueryBuilder, TestConfig } from '@/test-utils/util-functions';
import { IssueServiceProviders } from '../../../test-utils/providers/issue.service.providers';
import { ProjectEntity } from '../project/project.entity';
import {
CreateIssueDto,
FindIssuesByProjectIdDto,
Expand All @@ -38,6 +39,7 @@ import { IssueService } from './issue.service';
describe('IssueService test suite', () => {
let issueService: IssueService;
let issueRepo: Repository<IssueEntity>;
let projectRepo: Repository<ProjectEntity>;
let issueStatsRepo: Repository<IssueStatisticsEntity>;

beforeEach(async () => {
Expand All @@ -48,6 +50,7 @@ describe('IssueService test suite', () => {

issueService = module.get<IssueService>(IssueService);
issueRepo = module.get(getRepositoryToken(IssueEntity));
projectRepo = module.get(getRepositoryToken(ProjectEntity));
issueStatsRepo = module.get(getRepositoryToken(IssueStatisticsEntity));
});

Expand All @@ -61,6 +64,12 @@ describe('IssueService test suite', () => {

it('creating an issue succeeds with valid inputs', async () => {
dto.name = faker.string.sample();
jest.spyOn(projectRepo, 'findOne').mockResolvedValue({
id: faker.number.int(),
timezone: {
offset: '+09:00',
},
} as ProjectEntity);
jest.spyOn(issueRepo, 'findOneBy').mockResolvedValue(null as IssueEntity);
jest.spyOn(issueRepo, 'save').mockResolvedValue({
id: faker.number.int(),
Expand Down
29 changes: 23 additions & 6 deletions apps/api/src/domains/project/issue/issue.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,18 +197,35 @@ export class IssueService {

@Transactional()
async deleteById(id: number) {
const issue = new IssueEntity();
issue.id = id;
const issue = await this.repository.findOne({
where: { id },
relations: { project: true },
});

await this.issueStatisticsService.updateCount({
projectId: issue.project.id,
date: issue.createdAt,
count: -1,
});

await this.repository.remove(issue);
}

@Transactional()
async deleteByIds(ids: number[]) {
const issues = ids.map((id) => {
const issue = new IssueEntity();
issue.id = id;
return issue;
const issues = await this.repository.find({
where: { id: In(ids) },
relations: { project: true },
});

for (const issue of issues) {
await this.issueStatisticsService.updateCount({
projectId: issue.project.id,
date: issue.createdAt,
count: -1,
});
}

await this.repository.remove(issues);
}

Expand Down
Loading

0 comments on commit b304947

Please sign in to comment.