Skip to content
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

feat: delete board columns/handle votes #994

Merged
merged 4 commits into from
Feb 2, 2023
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
2 changes: 2 additions & 0 deletions backend/src/modules/boards/boards.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CommunicationModule } from 'src/modules/communication/communication.mod
import { SchedulesModule } from 'src/modules/schedules/schedules.module';
import TeamsModule from 'src/modules/teams/teams.module';
import UsersModule from 'src/modules/users/users.module';
import { VotesModule } from '../votes/votes.module';
import {
createBoardApplication,
createBoardService,
Expand All @@ -23,6 +24,7 @@ import BoardsController from './controller/boards.controller';
imports: [
UsersModule,
forwardRef(() => TeamsModule),
VotesModule,
SchedulesModule,
CommunicationModule,
mongooseBoardModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
updateBoardService
} from 'src/modules/boards/boards.providers';
import BoardsController from 'src/modules/boards/controller/boards.controller';
import { getCardService } from 'src/modules/cards/cards.providers';
import * as CommunicationsType from 'src/modules/communication/interfaces/types';
import {
createSchedulesService,
Expand All @@ -26,6 +27,7 @@ import {
teamUserRepository,
updateTeamService
} from 'src/modules/teams/providers';
import { deleteVoteService } from 'src/modules/votes/votes.providers';

describe('BoardsController', () => {
let controller: BoardsController;
Expand All @@ -52,6 +54,8 @@ describe('BoardsController', () => {
teamRepository,
teamUserRepository,
updateTeamService,
deleteVoteService,
getCardService,
{
provide: getModelToken('User'),
useValue: {}
Expand Down
12 changes: 2 additions & 10 deletions backend/src/modules/boards/controller/boards.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,16 +235,8 @@ export default class BoardsController {
@BoardUser([BoardRoles.RESPONSIBLE, TeamRoles.ADMIN, TeamRoles.STAKEHOLDER])
@UseGuards(BoardUserGuard)
@Put(':boardId')
async updateBoard(@Param() { boardId }: BaseParam, @Body() boardData: UpdateBoardDto) {
const board = await this.updateBoardApp.update(boardId, boardData);

if (!board) throw new BadRequestException(UPDATE_FAILED);

if (boardData.socketId) {
this.socketService.sendUpdatedBoard(boardId, boardData.socketId);
}

return board;
updateBoard(@Param() { boardId }: BaseParam, @Body() boardData: UpdateBoardDto) {
return this.updateBoardApp.update(boardId, boardData);
}

@ApiOperation({ summary: 'Delete a specific board' })
Expand Down
8 changes: 7 additions & 1 deletion backend/src/modules/boards/dto/update-board.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PartialType } from '@nestjs/mapped-types';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsOptional } from 'class-validator';
import BoardUser from '../schemas/board.user.schema';
import BoardDto from './board.dto';
Expand All @@ -8,4 +9,9 @@ export class UpdateBoardDto extends PartialType(BoardDto) {
@ApiPropertyOptional({ type: BoardUser, isArray: true })
@IsOptional()
responsible?: BoardUser;

@ApiProperty({ type: String, isArray: true })
@IsOptional()
@Type(() => String)
deletedColumns?: string[];
}
9 changes: 7 additions & 2 deletions backend/src/modules/boards/services/create.board.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export default class CreateBoardServiceImpl implements CreateBoardService {
const { team, recurrent, maxUsers, slackEnable, users, dividedBoards } = boardData;

const haveDividedBoards = dividedBoards.length > 0 ? true : false;
let newUsers = [];
const newUsers = [];

const newBoard = await this.createBoard(boardData, userId, false, haveDividedBoards);
let teamData;
Expand All @@ -155,7 +155,12 @@ export default class CreateBoardServiceImpl implements CreateBoardService {
}

if (!haveDividedBoards && !team) {
newUsers = [...users];
users.forEach((user) =>
newUsers.push({
...user,
votesCount: 0
})
);
}

await this.saveBoardUsers(newUsers, newBoard._id);
Expand Down
77 changes: 74 additions & 3 deletions backend/src/modules/boards/services/update.board.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CommunicationServiceInterface } from 'src/modules/communication/interfa
import * as CommunicationsType from 'src/modules/communication/interfaces/types';
import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface';
import * as Teams from 'src/modules/teams/interfaces/types';
import * as Votes from 'src/modules/votes/interfaces/types';
import User, { UserDocument } from 'src/modules/users/entities/user.schema';
import { UpdateBoardDto } from '../dto/update-board.dto';
import { ResponsibleType } from '../interfaces/responsible.interface';
Expand All @@ -25,6 +26,9 @@ import { BoardDataPopulate } from '../utils/populate-board';
import { UpdateColumnDto } from '../dto/column/update-column.dto';
import { UPDATE_FAILED } from 'src/libs/exceptions/messages';
import SocketGateway from 'src/modules/socket/gateway/socket.gateway';
import { DeleteVoteServiceInterface } from 'src/modules/votes/interfaces/services/delete.vote.service.interface';
import Column from '../schemas/column.schema';
import ColumnDto from '../dto/column/column.dto';

@Injectable()
export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterface {
Expand All @@ -36,7 +40,9 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
private slackCommunicationService: CommunicationServiceInterface,
@InjectModel(BoardUser.name)
private boardUserModel: Model<BoardUserDocument>,
private socketService: SocketGateway
private socketService: SocketGateway,
@Inject(Votes.TYPES.services.DeleteVoteService)
private deleteVoteService: DeleteVoteServiceInterface
) {}

/**
Expand Down Expand Up @@ -98,7 +104,7 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
* - is a sub-board
* - and the logged user isn't the current responsible
*/
if (boardData.users && currentResponsible.id !== newResponsible.id) {
if (boardData.users && String(currentResponsible.id) !== String(newResponsible.id)) {
if (isSubBoard) {
const promises = boardData.users
.filter((boardUser) =>
Expand Down Expand Up @@ -164,6 +170,64 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
board.addCards = boardData.addCards;
board.hideVotes = boardData.hideVotes;

/**
* Validate if:
* - have columns to delete
* Returns the votes to the user
*/
if (boardData.deletedColumns && !isEmpty(boardData.deletedColumns)) {
const cardsToDelete = boardData.deletedColumns.flatMap((deletedColumnId: string) => {
return board.columns.find((column) => column._id.toString() === deletedColumnId)?.cards;
});

cardsToDelete.forEach((cards) => {
cards.items.forEach(async (card) => {
const votesByUser = new Map<string, number>();

card.votes.forEach((userId) => {
if (!votesByUser.has(userId.toString())) {
votesByUser.set(userId.toString(), 1);
} else {
const count = votesByUser.get(userId.toString());

votesByUser.set(userId.toString(), count + 1);
}
});

votesByUser.forEach(async (votesCount, userId) => {
await this.deleteVoteService.decrementVoteUser(board.id, userId, -votesCount);
});
});
});
}

/**
* Only the regular boards will have their columns updated
*
* */

if (!isSubBoard && isEmpty(board.dividedBoards)) {
board.columns = boardData.columns.flatMap((col: Column | ColumnDto) => {
if (col._id) {
const columnBoard = board.columns.find((colBoard) => colBoard._id === col._id.toString());

if (columnBoard) {
return [{ ...columnBoard, title: col.title }];
}

const columnToDelete = boardData.deletedColumns.some(
(colId) => colId === col._id.toString()
);

if (columnToDelete) {
return [];
}
}

return [{ ...col }];
}) as Column[];
}

/**
* Only can change the maxVotes if:
* - new maxVotes not empty
Expand Down Expand Up @@ -197,9 +261,16 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
.lean()
.exec();

if (!updatedBoard) throw new BadRequestException(UPDATE_FAILED);

if (boardData.socketId) {
this.socketService.sendUpdatedBoard(boardId, boardData.socketId);
}

if (
updatedBoard &&
currentResponsible.id !== newResponsible.id &&
newResponsible &&
String(currentResponsible.id) !== String(newResponsible.id) &&
board.slackChannelId &&
updatedBoard.slackEnable &&
updatedBoard.isSubBoard
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/boardService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const createBoardRequest = (newBoard: CreateBoardDto): Promise<BoardType>
fetchData(`/boards`, { method: 'POST', data: newBoard });

export const updateBoardRequest = (
board: UpdateBoardType & { socketId: string },
board: UpdateBoardType & { socketId: string; deletedColumns?: string[] },
): Promise<BoardType> => fetchData(`/boards/${board._id}`, { method: 'PUT', data: board });

export const getBoardRequest = (
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/components/Board/RegularBoard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Container } from '@/styles/pages/boards/board.styles';
import DragDropArea from '@/components/Board/DragDropArea';
import LoadingPage from '@/components/loadings/LoadingPage';
import Flex from '@/components/Primitives/Flex';
import { boardInfoState, editColumnsState } from '@/store/board/atoms/board.atom';
import {
boardInfoState,
deletedColumnsState,
editColumnsState,
} from '@/store/board/atoms/board.atom';
import { BoardUserRoles } from '@/utils/enums/board.user.roles';
import Button from '@/components/Primitives/Button';
import Icon from '@/components/icons/Icon';
Expand All @@ -26,10 +30,14 @@ const RegularBoard = ({ socketId }: RegularBoardProps) => {
// Recoil States
const { board } = useRecoilValue(boardInfoState);
const setEditColumns = useSetRecoilState(editColumnsState);
const setDeletedColumns = useSetRecoilState(deletedColumnsState);

useMemo(() => {
if (!isOpen) setEditColumns(board.columns);
}, [board.columns, isOpen, setEditColumns]);
if (!isOpen) {
setEditColumns(board.columns);
setDeletedColumns([]);
}
}, [board.columns, isOpen, setDeletedColumns, setEditColumns]);

// Session Details
const { data: session } = useSession({ required: true });
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/Board/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import Separator from '@/components/Primitives/Separator';
import Text from '@/components/Primitives/Text';
import useBoard from '@/hooks/useBoard';
import SchemaUpdateBoard from '@/schema/schemaUpdateBoardForm';
import { boardInfoState, editColumnsState } from '@/store/board/atoms/board.atom';
import {
boardInfoState,
deletedColumnsState,
editColumnsState,
} from '@/store/board/atoms/board.atom';
import { UpdateBoardType } from '@/types/board/board';
import { BoardUserToAdd } from '@/types/board/board.user';
import { BoardUserRoles } from '@/utils/enums/board.user.roles';
Expand Down Expand Up @@ -73,6 +77,7 @@ const BoardSettings = ({
} = useRecoilValue(boardInfoState);

const [editColumns, setEditColumns] = useRecoilState(editColumnsState);
const deletedColumns = useRecoilValue(deletedColumnsState);

// State used to change values
const initialData: UpdateBoardType = {
Expand Down Expand Up @@ -265,6 +270,7 @@ const BoardSettings = ({
title,
maxVotes,
columns: isRegularBoard ? updatedColumns : data.columns,
deletedColumns,
socketId,
responsible: data.users?.find((user) => user.role === BoardUserRoles.RESPONSIBLE),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Text from '@/components/Primitives/Text';
// import { AlertDialogTrigger } from '@radix-ui/react-alert-dialog';
import Tooltip from '@/components/Primitives/Tooltip';
import Icon from '@/components/icons/Icon';
import { editColumnsState } from '@/store/board/atoms/board.atom';
import { deletedColumnsState, editColumnsState } from '@/store/board/atoms/board.atom';
import { useRecoilState } from 'recoil';
import {
AlertDialog,
Expand All @@ -13,6 +13,7 @@ import {
AlertDialogTrigger,
} from '@/components/Primitives/AlertDialog';
import Button from '@/components/Primitives/Button';
import ColumnType from '@/types/column';

interface Props {
columnTitle: string;
Expand All @@ -22,10 +23,15 @@ interface Props {

const DeleteColumnButton = ({ columnTitle, columnIndex, disableDeleteColumn }: Props) => {
const [editColumns, setEditColumns] = useRecoilState(editColumnsState);
const [deletedColumns, setDeletedColumns] = useRecoilState(deletedColumnsState);

const handleDeleteColumn = () => {
const arrayWithoutColumn = [...editColumns];
arrayWithoutColumn.splice(columnIndex, 1);

const column = arrayWithoutColumn.splice(columnIndex, 1)[0] as ColumnType;

setEditColumns(arrayWithoutColumn);
if (column._id) setDeletedColumns([...deletedColumns, column._id]);
};

return (
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/store/board/atoms/board.atom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ export const editColumnsState = atom<(ColumnType | CreateColumn)[]>({
key: 'editColumns',
default: [],
});

export const deletedColumnsState = atom<string[]>({
key: 'deletedColumns',
default: [],
});
2 changes: 1 addition & 1 deletion frontend/src/types/board/useBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default interface UseBoardType {
updateBoard: UseMutationResult<
BoardType,
unknown,
UpdateBoardType & { socketId: string },
UpdateBoardType & { socketId: string; deletedColumns?: string[] },
unknown
>;
deleteBoard: UseMutationResult<
Expand Down