Skip to content

feat: backlog 페이지 접속시 백로그 데이터 반환하도록 구현 #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions backend/src/project/dto/InitBacklogResponse.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Epic, EpicColor } from '../entity/epic.entity';
import { Story, StoryStatus } from '../entity/story.entity';
import { Task, TaskStatus } from '../entity/task.entity';

class TaskDto {
id: number;
displayId: number;
title: string;
expectedTime: number | null;
actualTime: number | null;
status: TaskStatus;
assignedMemberId: number | null;

static of(task: Task): TaskDto {
const dto = new TaskDto();
dto.id = task.id;
dto.displayId = task.displayId;
dto.title = task.title;
dto.expectedTime = task.expectedTime;
dto.actualTime = task.actualTime;
dto.status = task.status;
dto.assignedMemberId = task.assignedMemberId;
return dto;
}
}

class StoryDto {
id: number;
title: string;
point: number | null;
status: StoryStatus;
taskList: TaskDto[];

static of(story: Story): StoryDto {
const dto = new StoryDto();
dto.id = story.id;
dto.title = story.title;
dto.point = story.point;
dto.status = story.status;
dto.taskList = story.taskList.map(TaskDto.of);
return dto;
}
}

class EpicDto {
id: number;
name: string;
color: EpicColor;
storyList: StoryDto[];

static of(epic: Epic): EpicDto {
const dto = new EpicDto();
dto.id = epic.id;
dto.name = epic.name;
dto.color = epic.color;
dto.storyList = epic.storyList.map(StoryDto.of);
return dto;
}
}

class BacklogDto {
backlog: { epicList: EpicDto[] };

static of(epicList: Epic[]): BacklogDto {
const dto = new BacklogDto();
dto.backlog = { epicList: epicList.map(EpicDto.of) };
return dto;
}
}

export class InitBacklogResponseDto {
domain: string;
action: string;
content: BacklogDto;

static of(epicList: Epic[]): InitBacklogResponseDto {
const dto = new InitBacklogResponseDto();
dto.domain = 'backlog';
dto.action = 'init';
dto.content = BacklogDto.of(epicList);
return dto;
}
}
7 changes: 6 additions & 1 deletion backend/src/project/entity/epic.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Project } from './project.entity';
import { Story } from './story.entity';

export enum EpicColor {
YELLOW = 'yellow',
Expand All @@ -27,7 +29,7 @@ export class Epic {
@Column({ type: 'int', name: 'project_id' })
projectId: number;

@ManyToOne(() => Project, (project) => project.id, { nullable: false })
@ManyToOne(() => Project, (project) => project.epicList, { nullable: false })
@JoinColumn({ name: 'project_id' })
project: Project;

Expand All @@ -43,6 +45,9 @@ export class Epic {
@UpdateDateColumn({ type: 'timestamp' })
updated_at: Date;

@OneToMany(() => Story, (story) => story.epic)
storyList: Story[];

static of(project: Project, name: string, color: EpicColor) {
const newEpic = new Epic();
newEpic.project = project;
Expand Down
8 changes: 6 additions & 2 deletions backend/src/project/entity/project.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Generated,
JoinColumn,
} from 'typeorm';
import { Epic } from './epic.entity';
import { Link } from './link.entity.';
import { Memo } from './memo.entity';
import { ProjectToMember } from './project-member.entity';
Expand Down Expand Up @@ -45,14 +46,17 @@ export class Project {
@OneToMany(() => Link, (link) => link.id)
linkList: Link[];

@Column({type: 'int', nullable: false})
@Column({ type: 'int', nullable: false })
displayIdCount: number;

@OneToMany(() => Epic, (epic) => epic.project)
epicList: Epic[];

static of(title: string, subject: string) {
const newProject = new Project();
newProject.title = title;
newProject.subject = subject;
newProject.displayIdCount = 0;
newProject.displayIdCount = 0;
return newProject;
}
}
9 changes: 7 additions & 2 deletions backend/src/project/entity/story.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Epic } from './epic.entity';
import { Project } from './project.entity';
import { Task } from './task.entity';

export enum StoryStatus {
NotStarted = '시작전',
Expand All @@ -22,14 +24,14 @@ export class Story {
@Column({ type: 'int', name: 'project_id' })
projectId: number;

@ManyToOne(() => Project, (project) => project.id, { nullable: false })
@ManyToOne(() => Project, { nullable: false })
@JoinColumn({ name: 'project_id' })
project: Project;

@Column({ type: 'int', name: 'epic_id' })
epicId: number;

@ManyToOne(() => Epic, (epic) => epic.id, { nullable: false })
@ManyToOne(() => Epic, (epic) => epic.storyList, { nullable: false })
@JoinColumn({ name: 'epic_id' })
epic: Epic;

Expand All @@ -42,6 +44,9 @@ export class Story {
@Column({ type: 'varchar', length: 255, nullable: false })
status: StoryStatus;

@OneToMany(()=>Task, (task)=>task.story)
taskList: Task[];

static of(
project: Project,
epicId: number,
Expand Down
4 changes: 2 additions & 2 deletions backend/src/project/entity/task.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ export class Task {
@Column({ type: 'int', name: 'project_id', nullable: false })
projectId: number;

@ManyToOne(() => Project, (project) => project.id, { nullable: false })
@ManyToOne(() => Project, { nullable: false })
@JoinColumn({ name: 'project_id' })
project: Project;

@Column({ type: 'int', name: 'story_id', nullable: false })
storyId: number;

@ManyToOne(() => Story, (story) => story.id, { nullable: false })
@ManyToOne(() => Story, (story) => story.taskList, { nullable: false })
@JoinColumn({ name: 'story_id' })
story: Story;

Expand Down
7 changes: 7 additions & 0 deletions backend/src/project/project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,11 @@ export class ProjectRepository {
);
return !!result.affected;
}

getProjectBacklog(project: Project) {
return this.epicRepository.find({
where: { project: { id: project.id } },
relations: ['storyList', 'storyList.taskList'],
});
}
}
4 changes: 4 additions & 0 deletions backend/src/project/service/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,8 @@ export class ProjectService {
assignedMemberId,
);
}

getProjectBacklog(project: Project){
return this.projectRepository.getProjectBacklog(project);
}
}
9 changes: 4 additions & 5 deletions backend/src/project/ws-controller/ws-project.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { InitBacklogResponseDto } from '../dto/InitBacklogResponse.dto';
import { InitLandingResponseDto } from '../dto/InitLandingResponse.dto';
import { MemberUpdateNotifyDto } from '../dto/member/MemberUpdateNotify.dto';
import { MemberStatus } from '../enum/MemberStatus.enum';
Expand Down Expand Up @@ -53,10 +54,8 @@ export class WsProjectController {
async joinBacklogPage(client: ClientSocket) {
client.leave('landing');
client.join('backlog');
client.emit('backlog', {
domain: 'backlog',
action: 'init',
content: {},
});
const backlog = await this.projectService.getProjectBacklog(client.project);

client.emit('backlog', InitBacklogResponseDto.of(backlog));
}
}
146 changes: 146 additions & 0 deletions backend/test/project/ws-backlog-page/ws-init-backlog.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Socket } from 'socket.io-client';
import { app, appInit } from 'test/setup';
import {
getTwoMemberJoinedLandingPage,
} from '../ws-common';

describe('WS epic', () => {
beforeEach(async () => {
await app.close();
await appInit();
await app.listen(3000);
});

describe('backlog init', () => {
it('should return backlog data', async () => {
const [socket1, socket2] = await getTwoMemberJoinedLandingPage();
socket1.emit('joinBacklog');
await initBacklog(socket1);
const epicName = '회원';
const epicColor = 'yellow';
socket1.emit('epic', {
action: 'create',
content: { name: epicName, color: epicColor },
});
const epicId = await getEpicId(socket1);
const storyTitle = '타이틀';
const storyPoint = 2;
const storyStatus = '시작전';
socket1.emit('story', {
action: 'create',
content: {
title: storyTitle,
point: storyPoint,
status: storyStatus,
epicId,
},
});
const storyId = await getStoryId(socket1);
const taskTitle = '타이틀';
const taskStatus = '시작전';
const taskExpectedTime = 3.5;
const taskActualTime = 3.5;
const taskAssignedMemberId = null;

socket1.emit('task', {
action: 'create',
content: {
title: taskTitle,
expectedTime: taskExpectedTime,
actualTime: taskActualTime,
status: taskStatus,
assignedMemberId: taskAssignedMemberId,
storyId,
},
});

const { id: taskId, displayId: taskDisplayId } =
await getTaskIdAndDisplayId(socket1);

socket2.emit('joinBacklog');

await new Promise<void>((resolve, reject) => {
socket2.once('backlog', async (data) => {
const { domain, action, content } = data;
if (domain === 'backlog' && action === 'init') {
try {
expect(content.backlog.epicList).toHaveLength(1);
const epic = content.backlog.epicList[0];
expect(epic.id).toBe(epicId);
expect(epic.name).toBe(epicName);
expect(epic.color).toBe(epicColor);
expect(epic.storyList).toHaveLength(1);
const story = epic.storyList[0];
expect(story.id).toBe(storyId);
expect(story.title).toBe(storyTitle);
expect(story.point).toBe(storyPoint);
expect(story.status).toBe(storyStatus);
expect(story.taskList).toHaveLength(1);
const task = story.taskList[0];
expect(task.id).toBe(taskId);
expect(task.displayId).toBe(taskDisplayId);
expect(task.title).toBe(taskTitle);
expect(task.expectedTime).toBe(taskExpectedTime);
expect(task.actualTime).toBe(taskActualTime);
expect(task.status).toBe(taskStatus);
expect(task.assignedMemberId).toBe(taskAssignedMemberId);
} catch (e) {
reject(e);
}
resolve();
}
});
}).finally(() => {
socket1.close();
socket2.close();
});
});

const getEpicId = (socket) => {
return new Promise((resolve) => {
socket.once('backlog', async (data) => {
const { domain, action, content } = data;
if (domain === 'epic' && action === 'create') {
resolve(content.id);
return;
}
});
});
};

const getStoryId = (socket) => {
return new Promise((resolve) => {
socket.once('backlog', async (data) => {
const { domain, action, content } = data;
if (domain === 'story' && action === 'create') {
resolve(content.id);
return;
}
});
});
};

const getTaskIdAndDisplayId = (socket) => {
return new Promise<any>((resolve) => {
socket.once('backlog', async (data) => {
const { domain, action, content } = data;
if (domain === 'task' && action === 'create') {
resolve({ id: content.id, displayId: content.displayId });
return;
}
});
});
};
});
});

const initBacklog = async (socket: Socket) => {
return await new Promise<void>((resolve, reject) => {
socket.once('backlog', (data) => {
const { action, domain } = data;
if (action === 'init' && domain === 'backlog') {
resolve();
} else reject();
});
});
};
Loading