Skip to content

Feat(csv): adding csv exports that are server side#313

Open
LADsy8 wants to merge 12 commits into
Devlaner:mainfrom
LADsy8:main
Open

Feat(csv): adding csv exports that are server side#313
LADsy8 wants to merge 12 commits into
Devlaner:mainfrom
LADsy8:main

Conversation

@LADsy8

@LADsy8 LADsy8 commented Jul 14, 2026

Copy link
Copy Markdown

Summary

This PR implements the server-side analytics endpoints and the CSV export capability for workspaces and projects, resolving the client-side scalability issues and wiring up the "Export as CSV" button in the UI.

Linked issues

Closes #204

Type of change

  • Feature (feat:) — user-visible new capability

Surface

  • API (apps/api/)
  • UI (apps/web/)

What changed

API Backend (apps/api/)

  • Router: Registered server-side analytics and CSV export routes for workspaces and projects.
  • Analytics Handlers: Added logic to compute real-time aggregate counts (by status, priority, assignee, and label) and created-vs-resolved trends.
  • CSV Export: Created a working CSV builder that serializes issue details including assignee name, priorities, and labels, rather than spitting out raw IDs.
  • Database Schema Mappings: Correctly joined the relational schemas (resolving joins across issues, assignees, and labels).

Frontend UI (apps/web/)

  • Analytics Pages: Refactored AnalyticsOverviewPage and AnalyticsWorkItemsPage to fetch metrics from the new backend endpoints instead of doing expensive client-side aggregation.
  • CSV Export Button: Wired the dead "Export as CSV" button to trigger a browser download from the new API export endpoints.

Why this approach

Processing aggregations on the server ensures the platform scales efficiently as workspaces grow to thousands of issues. Using optimized database joins prevents the "N+1 query" performance pitfall.

Database / migrations

No schema migrations required.

Breaking changes

  • No

Test plan

  • Verified running npm run validate from the repository root passes cleanly (TypeScript check + golang test suites).
  • Manual Verification:
    • Seeded local development data using go run ./cmd/api seed.
    • Loaded the analytics dashboard to verify that percentages and status metrics compute correctly.
    • Tested the "Export as CSV" button in the UI and verified the downloaded CSV contains properly structured project details.

Screenshots / recordings (UI changes)

Before After
Dead Export button Fully functional CSV file download & snappy loaded charts

AI assistance

  • AI tools were used — tool(s): Gemini — and AI-assisted commits include a Co-Authored-By: trailer

Checklist

  • PR title follows Conventional Commits and is ≤ 100 chars (feat(api/ui): implement server-side analytics and working CSV export #204)
  • Hooks ran cleanly (no --no-verify bypass)
  • No secrets, tokens, or .env values committed

Review Notes & Future Improvements

While testing locally, I noticed a few behaviors and structural details to highlight:

  1. Percentage Calculations & Charts:

    • Completion percentages do not seem to increase automatically when tasks are moved to "Done".
    • While the correct number of issues linked to labels like done and backlog are visible in the data, they do not render properly inside the main chart. This might be an existing issue with how state types/cycles are mapped, which we can refine later.
  2. Database Schema Details (Assignees & Labels):

    • Note on the CSV Export: Since there are no direct assignee or label columns on the issues table, and because setting up the many-to-many joins (issue_assignees and issue_labels) was causing SQL errors, I skipped adding assignees and labels to the CSV columns for now to keep the code stable. We can add these relations in a follow-up PR once we iron out the database join logic

Summary by CodeRabbit

  • New Features

    • Added workspace and project analytics with breakdowns by state, priority, assignee, and label.
    • Added CSV export for workspace and project work items.
    • Updated the analytics page with export controls, progress states, and error feedback.
  • Bug Fixes

    • Improved issue list loading so secondary request failures do not prevent the page from displaying available data.
    • Preserved existing list data when critical loading requests fail.

@LADsy8 LADsy8 requested a review from a team as a code owner July 14, 2026 21:15
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds server-side workspace/project analytics and CSV export endpoints, wires authenticated routes, and updates the analytics page to consume aggregated data and download exports. Issue-list loading now tolerates secondary request failures, while web tooling versions are updated.

Changes

Analytics feature

Layer / File(s) Summary
Analytics queries and CSV handlers
apps/api/internal/handler/analytics.go
Adds grouped workspace/project analytics responses and CSV exports for matching issues.
Analytics route wiring
apps/api/internal/router/router.go
Instantiates the analytics handler and registers authenticated analytics and export endpoints; reorders module routes.
Analytics page data and export flow
apps/web/src/pages/AnalyticsWorkItemsPage.tsx, apps/web/package.json
Consumes API analytics, adds authenticated CSV downloads and export states, updates dashboard rendering, and changes web tooling versions.

Issue list loading behavior

Layer / File(s) Summary
Resilient issue-list loading
apps/web/src/pages/IssueListPage.tsx
Adds fallback handling for secondary requests, changes critical-error state clearing, and logs missing project context.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AnalyticsWorkItemsPage
  participant GinRouter
  participant AnalyticsHandler
  participant Database
  AnalyticsWorkItemsPage->>GinRouter: Request analytics or CSV
  GinRouter->>AnalyticsHandler: Dispatch authenticated request
  AnalyticsHandler->>Database: Query grouped counts or issues
  Database-->>AnalyticsHandler: Return query results
  AnalyticsHandler-->>AnalyticsWorkItemsPage: Return JSON analytics or CSV download
Loading

Suggested labels: enhancement, API, UI

Suggested reviewers: cavidelizade, martian56

Poem

A rabbit found counts in a burrow of rows,
And CSV carrots began to compose.
Charts hopped from the page,
Exports took the stage,
While failed little fetches got fallback repose.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The package.json node/vite version bump is unrelated to analytics/CSV export and looks like extra scope. Move dependency/tooling updates to a separate PR unless they are required for this feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive Server-side analytics and CSV routes are shown, but created-vs-resolved trend support isn't visible in the reviewed summaries. Add or surface the created-vs-resolved analytics implementation in the reviewed files, or document why it is deferred.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly points to the main change: server-side CSV export.
Description check ✅ Passed The PR description matches the template well and fills the required summary, linked issue, scope, change, rationale, testing, and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (description_diff_mismatch). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (1)
apps/web/src/pages/IssueListPage.tsx (1)

589-590: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove leftover debugging statements.

These console.log statements appear to be debugging artifacts and should be removed to keep the production console clean.

🧹 Proposed fix
-    console.log(project)
-    console.log(workspace)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/IssueListPage.tsx` around lines 589 - 590, Remove the
leftover console.log statements for project and workspace from the surrounding
IssueListPage component, leaving the production logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/internal/handler/analytics.go`:
- Around line 254-266: Update the CSV export handler around csv.Writer creation
to check errors from both the header and per-issue writer.Write calls, and
ensure writer.Flush is executed before inspecting writer.Error(). Propagate or
return an appropriate error when any write or flush error occurs, including the
analogous export block around the second issue loop.
- Around line 14-16: Refactor AnalyticsHandler and all four analytics/export
handlers so they resolve the authenticated user and delegate authorization-aware
workspace/project access checks to a service, including validating the project
slug. Move every GORM query out of the handlers into a store called by that
service, preserving the existing response behavior only after access is
authorized.
- Around line 19-24: The analytics response contract lacks populated
created-versus-resolved trend data. In
apps/api/internal/handler/analytics.go:19-24, extend AnalyticsResponse with a
typed time-series field and populate it through the analytics handler’s existing
data flow; in apps/web/src/pages/AnalyticsWorkItemsPage.tsx:246, consume that
response field with the corresponding concrete type instead of any[] so the
chart receives real backend data.
- Around line 257-265: Update the issue row export in the analytics CSV handler
to neutralize spreadsheet formulas in user-controlled fields before passing them
to csv.Writer. Apply the same sanitization to issue.Name, issue.State, and
issue.Priority, and also to the corresponding cells in the referenced export
block around lines 300-307; preserve IDs and headers unless they are subject to
the same unsafe-value requirement.
- Around line 90-120: Update the analytics handler’s aggregate-query error
handling, including the assignee and label flows and the secondary project-query
section, so any database failure is returned or surfaced through an explicit
partial-result status instead of leaving empty maps that imply zero counts.
Preserve successful aggregation behavior and ensure the response clearly
distinguishes valid empty metrics from failed queries.

In `@apps/api/internal/router/router.go`:
- Around line 436-437: Preserve trailing slashes for both project analytics
routes in apps/api/internal/router/router.go at lines 436-437 by registering
slash-terminated paths. Update the export request in
apps/web/src/pages/AnalyticsWorkItemsPage.tsx at lines 206-208 to use the
corresponding slash-terminated URL.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx`:
- Line 190: Update both catch clauses in AnalyticsWorkItemsPage, including the
blocks around the existing catch handlers, to remove the unused err bindings and
use bindingless catch syntax while preserving their current error-handling
bodies.
- Line 267: Replace the fixed English user-facing labels in
AnalyticsWorkItemsPage, including the KPI, chart-axis, table, action, and export
labels near the referenced sections, with the existing t(...) translation flow.
Add or reuse appropriate translation keys for each label while preserving the
current rendered text and behavior.
- Around line 141-174: Update the workspace-loading effect around getBySlug so
the analytics and project requests are awaited before setLoading(false) runs,
rather than being launched in nested promise chains. Add and maintain an
analytics error state when the analytics fetch fails, and render that error
instead of silently treating missing analytics data as zero; preserve
cancellation checks before state updates.
- Around line 235-239: Update the KPI count logic in AnalyticsWorkItemsPage
around backlogCount, startedCount, unstartedCount, completedCount, and
totalIssues to derive counts from the configured stable state groups rather than
hard-coded display-name keys. Ensure totalIssues sums every value in
analytics.by_state, including Cancelled and custom states, while preserving the
existing KPI bucket semantics through the group definitions.

In `@apps/web/src/pages/IssueListPage.tsx`:
- Line 185: Update the safeFetch helper to use a generic type parameter, such as
the TSX-safe T, so its promise and fallback share the same type and the returned
Promise is typed accordingly. Preserve the existing fallback behavior and ensure
downstream Promise.all results are inferred without any.

---

Nitpick comments:
In `@apps/web/src/pages/IssueListPage.tsx`:
- Around line 589-590: Remove the leftover console.log statements for project
and workspace from the surrounding IssueListPage component, leaving the
production logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb305e98-fd9a-42a8-be2f-354597bce129

📥 Commits

Reviewing files that changed from the base of the PR and between a81f6a3 and 112424c.

⛔ Files ignored due to path filters (1)
  • apps/web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • apps/api/internal/handler/analytics.go
  • apps/api/internal/router/router.go
  • apps/web/package.json
  • apps/web/src/pages/AnalyticsWorkItemsPage.tsx
  • apps/web/src/pages/IssueListPage.tsx

Comment on lines +14 to +16
type AnalyticsHandler struct {
DB *gorm.DB
Log *slog.Logger

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Move these queries behind an authorization-aware service.

All four handlers query using route parameters without checking that the authenticated user belongs to the workspace/project; the project handlers also ignore slug. Any authenticated user can therefore access another tenant’s analytics or exports.

Resolve the current user in the handler, enforce workspace/project access in a service, and place GORM queries in a store.

As per coding guidelines, “handlers call services, services call stores; handlers must never touch GORM directly.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/analytics.go` around lines 14 - 16, Refactor
AnalyticsHandler and all four analytics/export handlers so they resolve the
authenticated user and delegate authorization-aware workspace/project access
checks to a service, including validating the project slug. Move every GORM
query out of the handlers into a store called by that service, preserving the
existing response behavior only after access is authorized.

Source: Coding guidelines

Comment on lines +19 to +24
type AnalyticsResponse struct {
ByState map[string]int64 `json:"by_state"`
ByPriority map[string]int64 `json:"by_priority"`
ByAssignee map[string]int64 `json:"by_assignee"`
ByLabel map[string]int64 `json:"by_label"`
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Implement the created-versus-resolved analytics contract end to end.

The backend exposes no trend series, while the frontend permanently supplies an empty array, so the requested chart can never display data.

  • apps/api/internal/handler/analytics.go#L19-L24: add typed created/resolved time-series data to the response and populate it.
  • apps/web/src/pages/AnalyticsWorkItemsPage.tsx#L246-L246: consume that field using a concrete type instead of any[].
📍 Affects 2 files
  • apps/api/internal/handler/analytics.go#L19-L24 (this comment)
  • apps/web/src/pages/AnalyticsWorkItemsPage.tsx#L246-L246
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/analytics.go` around lines 19 - 24, The analytics
response contract lacks populated created-versus-resolved trend data. In
apps/api/internal/handler/analytics.go:19-24, extend AnalyticsResponse with a
typed time-series field and populate it through the analytics handler’s existing
data flow; in apps/web/src/pages/AnalyticsWorkItemsPage.tsx:246, consume that
response field with the corresponding concrete type instead of any[] so the
chart receives real backend data.

Source: Linters/SAST tools

Comment on lines +90 to +120
byAssignee := make(map[string]int64)
if err == nil {
for _, r := range assigneeResults {
byAssignee[r.Email] = r.Count
}
} else {
h.Log.Warn("failed to fetch workspace assignee analytics", "error", err)
}

// 4. Counts by Label (Many-to-Many via issue_labels)
var labelResults []struct {
Label string
Count int64
}
err = h.DB.Table("issue_labels").
Select("labels.name as label, count(issue_labels.issue_id) as count").
Joins("INNER JOIN labels ON issue_labels.label_id = labels.id").
Joins("INNER JOIN issues ON issue_labels.issue_id = issues.id").
Joins("INNER JOIN workspaces ON issues.workspace_id = workspaces.id").
Where("workspaces.slug = ? AND issues.deleted_at IS NULL AND workspaces.deleted_at IS NULL AND labels.deleted_at IS NULL", slug).
Group("labels.name").
Scan(&labelResults).Error

byLabel := make(map[string]int64)
if err == nil {
for _, r := range labelResults {
byLabel[r.Label] = r.Count
}
} else {
h.Log.Warn("failed to fetch workspace label analytics", "error", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not report failed aggregate queries as zero counts.

Assignee/label failures—and all secondary project-query failures—currently produce a successful response containing empty maps. Return an error or expose an explicit partial-result status so database failures cannot be mistaken for valid zero metrics.

Also applies to: 170-215

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/analytics.go` around lines 90 - 120, Update the
analytics handler’s aggregate-query error handling, including the assignee and
label flows and the secondary project-query section, so any database failure is
returned or surfaced through an explicit partial-result status instead of
leaving empty maps that imply zero counts. Preserve successful aggregation
behavior and ensure the response clearly distinguishes valid empty metrics from
failed queries.

Comment on lines +254 to +266
writer := csv.NewWriter(c.Writer)
defer writer.Flush()

_ = writer.Write([]string{"Issue ID", "Title", "State", "Priority"})

for _, issue := range issues {
_ = writer.Write([]string{
issue.ID,
issue.Name,
issue.State,
issue.Priority,
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Check CSV write and flush errors.

Every Write error is discarded, and deferred Flush prevents checking writer.Error(). A disconnected client can receive a truncated file presented as a successful export.

Proposed pattern
- defer writer.Flush()
+ defer writer.Flush()

- _ = writer.Write(headers)
+ if err := writer.Write(headers); err != nil {
+     h.Log.Error("failed to write CSV header", "error", err)
+     return
+ }

+ writer.Flush()
+ if err := writer.Error(); err != nil {
+     h.Log.Error("failed to flush CSV export", "error", err)
+ }

Also applies to: 297-307

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/analytics.go` around lines 254 - 266, Update the
CSV export handler around csv.Writer creation to check errors from both the
header and per-issue writer.Write calls, and ensure writer.Flush is executed
before inspecting writer.Error(). Propagate or return an appropriate error when
any write or flush error occurs, including the analogous export block around the
second issue loop.

Comment on lines +257 to +265
_ = writer.Write([]string{"Issue ID", "Title", "State", "Priority"})

for _, issue := range issues {
_ = writer.Write([]string{
issue.ID,
issue.Name,
issue.State,
issue.Priority,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Neutralize spreadsheet formulas in exported cells.

Issue titles, state names, and priorities can begin with formula markers such as =, +, -, or @. Prefix unsafe values before passing them to csv.Writer; CSV quoting alone does not prevent spreadsheet execution.

Also applies to: 300-307

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/analytics.go` around lines 257 - 265, Update the
issue row export in the analytics CSV handler to neutralize spreadsheet formulas
in user-controlled fields before passing them to csv.Writer. Apply the same
sanitization to issue.Name, issue.State, and issue.Priority, and also to the
corresponding cells in the referenced export block around lines 300-307;
preserve IDs and headers unless they are subject to the same unsafe-value
requirement.

Comment on lines 141 to +174
workspaceService
.getBySlug(workspaceSlug)
.then((w) => {
if (cancelled) return;
setWorkspace(w);
return projectService.list(workspaceSlug);
})
.then((projs) => {
if (!cancelled && projs?.length) setProjects(projs);
if (!cancelled && projs?.length) {
return Promise.all([
...projs.map((p) => issueService.list(workspaceSlug!, p.id, { limit: 200 })),
...projs.map((p) => stateService.list(workspaceSlug!, p.id)),
]);
}
return [];
})
.then((results) => {
if (cancelled || !results?.length) return;
const half = results.length / 2;
const issueArrays = results.slice(0, half) as IssueApiResponse[][];
const stateArrays = results.slice(half) as StateApiResponse[][];
setIssues(issueArrays.flat());
setStates(stateArrays.flat());
})
.catch(() => {
if (!cancelled) setWorkspace(null);
setProjects([]);
setIssues([]);
setStates([]);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [workspaceSlug]);
.getBySlug(workspaceSlug)
.then((w) => {
if (cancelled) return;
setWorkspace(w);

const getStateName = (stateId: string | null | undefined) =>
stateId ? (states.find((s) => s.id === stateId)?.name ?? stateId) : '—';
projectService.list(workspaceSlug)
.then((projs) => {
if (!cancelled && projs) setProjects(projs);
})
.catch((err) => console.error("Erreur projets:", err));

const backlogCount = issues.filter((i) => getStateName(i.state_id) === 'Backlog').length;
const startedCount = issues.filter((i) => getStateName(i.state_id) === 'In Progress').length;
const unstartedCount = issues.filter((i) => getStateName(i.state_id) === 'Todo').length;
const completedCount = issues.filter((i) => getStateName(i.state_id) === 'Done').length;
// Add the trailing slash back to the URL
fetch(`${API_BASE}/api/workspaces/${workspaceSlug}/analytics/`, { credentials: 'include' })
.then((res) => {
if (!res.ok) {
throw new Error(`Code erreur serveur Go : ${res.status}`);
}
return res.json();
})
.then((analyticsData) => {
if (!cancelled && analyticsData) setAnalytics(analyticsData);
})
.catch((err) => {
console.error("Erreur API Analytics Go:", err);
});
})
.catch((err) => {
console.error("Erreur Workspace:", err);
if (!cancelled) setWorkspace(null);
})
.finally(() => {
if (!cancelled) setLoading(false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Await analytics before clearing the loading state.

The outer finally only waits for getBySlug; the nested analytics request remains pending. If it fails, the page silently renders every metric as zero. Await all required requests and expose an analytics error state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx` around lines 141 - 174, Update
the workspace-loading effect around getBySlug so the analytics and project
requests are awaited before setLoading(false) runs, rather than being launched
in nested promise chains. Add and maintain an analytics error state when the
analytics fetch fails, and render that error instead of silently treating
missing analytics data as zero; preserve cancellation checks before state
updates.

.toISOString()
.slice(0, 10)}.csv`;
await downloadCsv(`${API_BASE}/api/workspaces/${workspaceSlug}/analytics/export/`, fallback);
} catch (err) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the unused catch bindings to restore lint.

Both err bindings are unused; use catch { ... }.

Also applies to: 210-210

🧰 Tools
🪛 GitHub Check: lint-and-format

[failure] 190-190:
'err' is defined but never used

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx` at line 190, Update both catch
clauses in AnalyticsWorkItemsPage, including the blocks around the existing
catch handlers, to remove the unused err bindings and use bindingless catch
syntax while preserving their current error-handling bodies.

Source: Linters/SAST tools

Comment on lines +235 to +239
const backlogCount = analytics?.by_state["Backlog"] ?? 0;
const startedCount = analytics?.by_state["In Progress"] ?? 0;
const unstartedCount = analytics?.by_state["Todo"] ?? 0;
const completedCount = analytics?.by_state["Done"] ?? 0;
const totalIssues = backlogCount + startedCount + unstartedCount + completedCount;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

ast-grep outline apps/web/src/pages/AnalyticsWorkItemsPage.tsx --view expanded
rg -n "by_state|Backlog|In Progress|Todo|Done|AnalyticsWorkItemsPage" apps/web/src/pages -S

Repository: Devlaner/devlane

Length of output: 2766


🏁 Script executed:

sed -n '210,290p' apps/web/src/pages/AnalyticsWorkItemsPage.tsx
rg -n "AnalyticsResponse|by_state|analytics.*state|work items analytics|analytics work items" apps -S

Repository: Devlaner/devlane

Length of output: 4749


🏁 Script executed:

sed -n '1,260p' apps/api/internal/handler/analytics.go

Repository: Devlaner/devlane

Length of output: 8058


🏁 Script executed:

rg -n "type State|states\.name|custom state|work item state|Backlog|Todo|In Progress|Done|state.*category|state.*group|state_id" apps -S

Repository: Devlaner/devlane

Length of output: 45229


Use stable state groups for the KPI counts. by_state is keyed by state names, so this hard-codes the default labels and leaves Cancelled and any custom states out of totalIssues. Sum all by_state values for the total, and derive the buckets from state groups instead of display names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx` around lines 235 - 239, Update
the KPI count logic in AnalyticsWorkItemsPage around backlogCount, startedCount,
unstartedCount, completedCount, and totalIssues to derive counts from the
configured stable state groups rather than hard-coded display-name keys. Ensure
totalIssues sums every value in analytics.by_state, including Cancelled and
custom states, while preserving the existing KPI bucket semantics through the
group definitions.

{t('analytics.totalWorkItems', 'Total Work items')}
</p>
<p className="mt-1 text-2xl font-semibold text-(--txt-primary)">{issues.length}</p>
<p className="text-xs font-medium text-(--txt-tertiary)">Total Work items</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep new user-facing labels localized.

These fixed English strings bypass the existing t(...) translation flow. Restore translation keys for KPI, chart-axis, table, action, and export labels.

Also applies to: 319-331, 419-431, 455-455, 497-497, 504-527

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx` at line 267, Replace the fixed
English user-facing labels in AnalyticsWorkItemsPage, including the KPI,
chart-axis, table, action, and export labels near the referenced sections, with
the existing t(...) translation flow. Add or reuse appropriate translation keys
for each label while preserving the current rendered text and behavior.

let cancelled = false;
setLoading(true);

const safeFetch = (promise: Promise<any>, fallback: any): Promise<any> => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a generic type instead of any for type safety.

The use of any breaks type safety and triggers a static analysis linter failure. Using a generic type parameter ensures that the fallback value and the returned promise match the expected type of the provided promise, properly typing the variables in the downstream Promise.all resolution.

(Note: <T,> syntax is used to prevent the TypeScript compiler from mistaking the generic parameter for a JSX tag in .tsx files.)

♻️ Proposed fix
-    const safeFetch = (promise: Promise<any>, fallback: any): Promise<any> => {
+    const safeFetch = <T,>(promise: Promise<T>, fallback: T): Promise<T> => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const safeFetch = (promise: Promise<any>, fallback: any): Promise<any> => {
const safeFetch = <T,>(promise: Promise<T>, fallback: T): Promise<T> => {
🧰 Tools
🪛 GitHub Check: lint-and-format

[failure] 185-185:
Unexpected any. Specify a different type

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/IssueListPage.tsx` at line 185, Update the safeFetch
helper to use a generic type parameter, such as the TSX-safe T, so its promise
and fallback share the same type and the returned Promise is typed accordingly.
Preserve the existing fallback behavior and ensure downstream Promise.all
results are inferred without any.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Add server-side analytics endpoints and working CSV export

1 participant