Skip to content

Commit 94789eb

Browse files
authored
Merge pull request #326 from Trendyol/teams-client
teams-client implementations for planner
2 parents 5253090 + 559679b commit 94789eb

File tree

14 files changed

+646
-74
lines changed

14 files changed

+646
-74
lines changed

gurubu-backend/controllers/plannerJiraController.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ dotenv.config();
55

66
exports.getFutureSprints = async (req, res) => {
77
try {
8-
const boardId = process.env.JIRA_DEFAULT_BOARD_ID;
8+
const boardId = parseInt(req.params.boardId);
99
const sprints = await jiraService.getFutureSprints(boardId);
1010
res.json(sprints);
1111
} catch (error) {
@@ -39,16 +39,27 @@ exports.getSprintIssues = async (req, res) => {
3939
exports.getSprintStatistics = async (req, res) => {
4040
try {
4141
const sprintId = parseInt(req.params.sprintId);
42+
const { assignees } = req.body;
4243

4344
if (isNaN(sprintId)) {
4445
return res.status(400).json({ error: "Invalid sprint ID" });
4546
}
4647

47-
const statistics = await jiraService.getSprintStatistics(sprintId);
48+
if (!assignees) {
49+
return res.status(400).json({ error: "Assignees data is required" });
50+
}
51+
52+
const issues = await jiraService.getSprintIssues(sprintId);
53+
const statistics = jiraService.calculateSprintStatistics(sprintId, issues.issues, assignees);
4854
res.json(statistics);
4955
} catch (error) {
50-
console.error("Error getting sprint statistics:", error);
51-
res.status(500).json({ error: "Failed to get sprint statistics" });
56+
console.error("Error getting sprint statistics:", {
57+
error: error.message,
58+
stack: error.stack,
59+
sprintId: req.params.sprintId,
60+
assignees: req.body.assignees
61+
});
62+
res.status(500).json({ error: error.message || "Failed to get sprint statistics" });
5263
}
5364
};
5465

gurubu-backend/routes/jiraRoutes.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ const plannerJiraController = require("../controllers/plannerJiraController");
55

66
router.get("/fetch", jiraController.fetchGet);
77
router.put("/fetch", jiraController.fetchPut);
8-
router.get("/future", plannerJiraController.getFutureSprints);
8+
router.get("/:boardId/future", plannerJiraController.getFutureSprints);
99
router.get("/board", plannerJiraController.getBoardByName);
1010
router.get("/:sprintId/issues", plannerJiraController.getSprintIssues);
11-
router.get("/:sprintId/statistics", plannerJiraController.getSprintStatistics);
11+
router.post("/:sprintId/statistics", plannerJiraController.getSprintStatistics);
1212

1313
module.exports = router;

gurubu-backend/services/jiraService.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,13 @@ class JiraService {
6464
};
6565
}
6666

67-
calculateSprintStatistics(sprintId, issues) {
67+
calculateSprintStatistics(sprintId, issues, assigneesData) {
6868
let parsedAssignees = {};
6969

7070
try {
71-
const assignees = process.env.JIRA_DEFAULT_ASSIGNEES
72-
? Buffer.from(process.env.JIRA_DEFAULT_ASSIGNEES, 'base64').toString('utf-8')
73-
: "{}"; // Default to an empty JSON string
74-
75-
parsedAssignees = JSON.parse(assignees); // Convert string to object
71+
parsedAssignees = assigneesData || {};
7672
} catch (error) {
77-
console.error("Failed to parse JIRA_DEFAULT_ASSIGNEES:", error);
73+
console.error("Failed to parse assignees data:", error);
7874
}
7975
// Initialize statistics for all assignees
8076
const statisticsMap = new Map();
@@ -222,7 +218,8 @@ class JiraService {
222218
async getSprintStatistics(sprintId) {
223219
try {
224220
const issues = await this.getSprintIssues(sprintId);
225-
return this.calculateSprintStatistics(sprintId, issues.issues);
221+
const assigneesData = req.body.assignees;
222+
return this.calculateSprintStatistics(sprintId, issues.issues, assigneesData);
226223
} catch (error) {
227224
if (axios.isAxiosError(error)) {
228225
console.error("Axios error details:", {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import PlannerTable from './PlannerTable';
5+
import { SelectTeamForm } from './SelectTeamForm';
6+
import { Sprint } from '../components/SprintDropdown';
7+
import { Modal } from '@/components/common/modal';
8+
import { IconUsers } from '@tabler/icons-react';
9+
10+
interface PlannerContentProps {
11+
setSprints: (sprint: Sprint[]) => void;
12+
selectedSprintId: number | null;
13+
refreshTrigger: number;
14+
handleRefresh: () => void;
15+
selectedSprint: Sprint | null;
16+
onSprintSelect: (sprint: Sprint | null) => void;
17+
showTeamSelect: boolean;
18+
handleCloseTeamSelect: () => void;
19+
handleTeamSelectClick: () => void;
20+
hasTeamSelected: boolean;
21+
loading: boolean;
22+
setLoading: (value: boolean) => void;
23+
}
24+
25+
export const PlannerContent: React.FC<PlannerContentProps> = ({
26+
setSprints,
27+
selectedSprintId,
28+
refreshTrigger,
29+
handleRefresh,
30+
selectedSprint,
31+
onSprintSelect,
32+
showTeamSelect,
33+
handleCloseTeamSelect,
34+
handleTeamSelectClick,
35+
hasTeamSelected,
36+
loading,
37+
setLoading
38+
}) => {
39+
return (
40+
<div className="gurubu-planner-content">
41+
<Modal isOpen={showTeamSelect} onClose={handleCloseTeamSelect}>
42+
<SelectTeamForm
43+
setSprints={setSprints}
44+
handleRefresh={handleRefresh}
45+
closeModal={handleCloseTeamSelect}
46+
setLoading={setLoading}
47+
/>
48+
</Modal>
49+
50+
{!hasTeamSelected ? (
51+
<div className="no-team-message">
52+
<IconUsers size={48} />
53+
<h3>No Team Selected</h3>
54+
<p>Please select a team to view sprint statistics</p>
55+
<button onClick={handleTeamSelectClick}>Select Team</button>
56+
</div>
57+
) : (
58+
<PlannerTable
59+
selectedSprintId={selectedSprintId}
60+
refreshTrigger={refreshTrigger}
61+
loading={loading}
62+
setLoading={setLoading}
63+
/>
64+
)}
65+
</div>
66+
);
67+
};

gurubu-client/src/app/gurubu-planner/components/PlannerTable.tsx

+23-12
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ interface SprintStatisticsResponse {
3535
interface PlannerTableProps {
3636
selectedSprintId: number | null;
3737
refreshTrigger: number;
38-
onLoadingChange?: (isLoading: boolean) => void;
38+
setLoading: (isLoading: boolean) => void;
39+
loading: boolean;
3940
}
4041

4142
const LoadingSkeleton = () => {
@@ -79,10 +80,10 @@ const LoadingSkeleton = () => {
7980
const PlannerTable: React.FC<PlannerTableProps> = ({
8081
selectedSprintId,
8182
refreshTrigger,
82-
onLoadingChange
83+
setLoading,
84+
loading
8385
}) => {
8486
const [statistics, setStatistics] = React.useState<SprintStatisticsResponse | null>(null);
85-
const [isLoading, setIsLoading] = React.useState(false);
8687
const [error, setError] = React.useState<string | null>(null);
8788

8889
const fetchControllerRef = React.useRef<AbortController | null>(null);
@@ -103,34 +104,44 @@ const PlannerTable: React.FC<PlannerTableProps> = ({
103104
const controller = new AbortController();
104105
fetchControllerRef.current = controller;
105106

106-
setIsLoading(true);
107-
onLoadingChange?.(true);
107+
setLoading(true);
108108
setError(null);
109109

110110
try {
111+
const assigneesData = localStorage.getItem('JIRA_DEFAULT_ASSIGNEES');
112+
if (!assigneesData) {
113+
throw new Error('No team selected');
114+
}
115+
116+
const assignees = JSON.parse(assigneesData);
111117
const response = await fetch(
112118
`${process.env.NEXT_PUBLIC_API_URL}/jira/${selectedSprintId}/statistics`,
113119
{
120+
method: 'POST',
114121
signal: controller.signal,
115122
headers: {
116123
'Cache-Control': 'no-cache',
117-
'Pragma': 'no-cache'
118-
}
124+
'Pragma': 'no-cache',
125+
'Content-Type': 'application/json'
126+
},
127+
body: JSON.stringify({ assignees })
119128
}
120129
);
121130
if (!response.ok) {
122-
throw new Error('Failed to fetch sprint statistics');
131+
const errorData = await response.json();
132+
console.error('Sprint statistics error:', errorData);
133+
throw new Error(errorData.error || 'Failed to fetch sprint statistics');
123134
}
124135
const data: SprintStatisticsResponse = await response.json();
125136
setStatistics(data);
137+
setLoading(false);
126138
} catch (err) {
127139
if (err instanceof Error && err.name === 'AbortError') {
128140
return;
129141
}
142+
setLoading(false);
130143
setError(err instanceof Error ? err.message : 'An error occurred');
131144
} finally {
132-
setIsLoading(false);
133-
onLoadingChange?.(false);
134145
if (controller === fetchControllerRef.current) {
135146
fetchControllerRef.current = null;
136147
}
@@ -149,7 +160,7 @@ const PlannerTable: React.FC<PlannerTableProps> = ({
149160

150161
return (
151162
<div className="gurubu-planner-table">
152-
{isLoading ? (
163+
{loading ? (
153164
<LoadingSkeleton />
154165
) : error ? (
155166
<div className="gurubu-planner-table-error">
@@ -170,7 +181,7 @@ const PlannerTable: React.FC<PlannerTableProps> = ({
170181
<div className="body-cell">
171182
<div className="assignee-info">
172183
<IconUserFilled size={24} className="assignee-avatar" />
173-
<span>{stat.assignee.name}</span>
184+
<span>{stat.assignee.displayName}</span>
174185
</div>
175186
</div>
176187
<div className="body-cell">{stat.assignedTasks.length || stat.testTasks.length }</div>

0 commit comments

Comments
 (0)