Skip to content

feat(csv): added the csv export to the analytics page#393

Closed
LADsy8 wants to merge 25 commits into
Devlaner:mainfrom
LADsy8:correctingPR
Closed

feat(csv): added the csv export to the analytics page#393
LADsy8 wants to merge 25 commits into
Devlaner:mainfrom
LADsy8:correctingPR

Conversation

@LADsy8

@LADsy8 LADsy8 commented Jul 22, 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 server-provided workspace and project analytics (state, priority, assignee, label) to power the analytics KPIs/charts.
    • Added CSV exports for workspace and project issue analytics.
  • Bug Fixes
    • Improved resilience on the issue list page by tolerating failures in secondary requests without crashing.
    • Analytics now provide consistent access/lookup handling and can return partial results when some breakdowns fail.
  • Security
    • Hardened CSV exports against spreadsheet formula injection and improved safe CSV filename generation.
  • Chores
    • Updated web runtime/development dependency versions.

@LADsy8
LADsy8 requested a review from a team as a code owner July 22, 2026 10:49
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 43b83b53-2504-4c92-a288-d1cbbee85403

📥 Commits

Reviewing files that changed from the base of the PR and between 68ae41d and df95619.

📒 Files selected for processing (6)
  • apps/api/internal/handler/analytics.go
  • apps/api/internal/router/router.go
  • apps/api/internal/service/analytics.go
  • apps/api/internal/store/analytics.go
  • apps/web/src/pages/AnalyticsWorkItemsPage.tsx
  • apps/web/src/pages/IssueListPage.tsx

📝 Walkthrough

Walkthrough

Adds server-side workspace and project analytics with authorization, partial aggregation, and CSV export endpoints. The web analytics page now consumes backend analytics and provides workspace and project exports. Issue-list loading, web package versions, and module route ordering are also updated.

Changes

Analytics feature

Layer / File(s) Summary
Analytics contracts, authorization, and aggregation
apps/api/internal/model/analytics.go, apps/api/internal/store/analytics.go, apps/api/internal/service/analytics.go
Defines analytics and export models, queries grouped issue data, validates workspace/project membership, and returns aggregated analytics maps.
CSV export pipeline
apps/api/internal/store/analytics.go, apps/api/internal/service/analytics.go, apps/api/internal/handler/analytics.go
Queries authorized issue rows, writes workspace/project CSV responses, and sanitizes formula-triggering cell values.
HTTP analytics handlers
apps/api/internal/handler/analytics.go
Adds authenticated workspace and project analytics handlers with UUID validation and HTTP error mapping.
Authenticated analytics route wiring
apps/api/internal/router/router.go
Constructs analytics components and registers workspace/project analytics and CSV export routes.
Analytics page API integration and exports
apps/web/src/pages/AnalyticsWorkItemsPage.tsx
Loads backend analytics, derives KPIs and charts, and adds workspace/project CSV download controls.

Ancillary application updates

Layer / File(s) Summary
Best-effort secondary list loading
apps/web/src/pages/IssueListPage.tsx
Adds fallbacks for non-critical list requests while preserving critical workspace and project loading failures.
Web package updates
apps/web/package.json
Adds the Node package entry and updates Vite.
Module route registration order
apps/api/internal/router/router.go
Registers modules-progress before modules.

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

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant AnalyticsAPI
  participant AnalyticsService
  participant AnalyticsStore
  Browser->>AnalyticsAPI: Request workspace or project analytics
  AnalyticsAPI->>AnalyticsService: Pass authenticated analytics request
  AnalyticsService->>AnalyticsStore: Validate access and query grouped counts
  AnalyticsStore-->>AnalyticsService: Return analytics rows
  AnalyticsService-->>AnalyticsAPI: Return analytics response
  AnalyticsAPI-->>Browser: Render analytics data
  Browser->>AnalyticsAPI: Request CSV export
  AnalyticsAPI->>AnalyticsService: Pass authenticated export request
  AnalyticsService->>AnalyticsStore: Query authorized issue rows
  AnalyticsStore-->>AnalyticsAPI: Return export rows
  AnalyticsAPI-->>Browser: Download CSV attachment
Loading

Possibly related PRs

  • Devlaner/devlane#313: Directly overlaps with the analytics backend, handlers, routes, frontend page, and CSV export flow.

Suggested labels: API, Vulnerability

Poem

I’m a rabbit with charts in my den,
Counting work items again.
CSV carrots stream,
Trends hop through a dream,
While exports go thump-thump-thump!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds analytics endpoints and CSV export, but the requested created-vs-resolved trend analytics are not evident in the code summary. Add the missing created-vs-resolved analytics path or clearly document it as deferred, and align the implementation with issue #204.
Out of Scope Changes check ⚠️ Warning Unrelated changes were included in apps/web/package.json and IssueListPage.tsx that are outside the analytics and CSV export scope. Move the dependency bump and IssueListPage resilience changes into a separate PR unless they are required for this feature.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and relevant to the CSV export work, though it understates the broader server-side analytics changes.
Description check ✅ Passed The PR description follows the template and includes the required sections with mostly complete content.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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.

@LADsy8 LADsy8 changed the title Correcting pr feat(csv): added the csv export to the analytics page Jul 22, 2026

@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: 4

🧹 Nitpick comments (2)
apps/api/internal/service/analytics.go (1)

110-115: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Inconsistent priority-error handling.

In GetProjectAnalytics, a failure from GetProjectPriorityAnalytics is silently swallowed (no s.log.Warn, no partial_error), whereas the workspace path treats a priority failure as fatal (return nil, err). Consider logging a warning here so a failed priority aggregate is observable rather than silently returning an empty map.

🤖 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/service/analytics.go` around lines 110 - 115, Update
GetProjectAnalytics so errors from GetProjectPriorityAnalytics are not silently
ignored: log a warning through s.log.Warn while preserving the existing partial
analytics response and empty priority map behavior. Keep successful priority
aggregation unchanged.
apps/web/src/pages/AnalyticsWorkItemsPage.tsx (1)

185-192: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: surface partial_error/warnings to the user.

The response type declares partial_error/warnings, and the backend sets them when assignee/label/trend aggregates fail, but they are never read here. When only secondary aggregates fail the page renders silently with missing/empty sections (e.g. an empty trend chart) and no indication anything is degraded. Consider showing the warnings when partial_error is true.

🤖 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 185 - 192, Update
the fulfilled analytics handling in the page’s analytics-loading flow to inspect
the response’s partial_error and warnings fields. When partial_error is true,
surface the available warnings to the user while still setting the analytics
result; preserve the existing full-request failure handling for rejected
responses.
🤖 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 57-77: Replace the raw *gorm.DB dependency and direct queries in
AnalyticsHandler.GetWorkspaceAnalytics and the other analytics/export handlers
with an AnalyticsService dependency; pass middleware.GetUser(c).ID to service
methods so their existing workspace/project authorization guards run before
aggregation. In apps/api/internal/handler/analytics.go lines 57-77, route the
workspace analytics request through AnalyticsService; in
apps/api/internal/router/router.go lines 248-251, construct
service.NewAnalyticsService(...) and inject it into AnalyticsHandler instead of
cfg.DB.

In `@apps/api/internal/service/analytics.go`:
- Around line 1-6: Add the required package declaration and import block before
analyticsService, importing the packages needed by the file, including fmt,
log/slog, and gorm.io/gorm. Remove the trailing whitespace from the db field and
run gofmt so analytics.go parses and builds cleanly.
- Around line 17-45: Update checkWorkspaceAccess and checkProjectAccess so their
workspace_members joins or conditions require workspace_members.deleted_at IS
NULL. Preserve the existing authorization and project/workspace deletion checks
while ensuring soft-deleted memberships cannot satisfy either analytics access
check.

In `@apps/web/package.json`:
- Line 47: Remove the "node" dependency from apps/web/package.json and declare
the supported Node version through the package's engines field or the
repository's existing toolchain/CI configuration, preserving the intended
version range without installing a local Node binary.

---

Nitpick comments:
In `@apps/api/internal/service/analytics.go`:
- Around line 110-115: Update GetProjectAnalytics so errors from
GetProjectPriorityAnalytics are not silently ignored: log a warning through
s.log.Warn while preserving the existing partial analytics response and empty
priority map behavior. Keep successful priority aggregation unchanged.

In `@apps/web/src/pages/AnalyticsWorkItemsPage.tsx`:
- Around line 185-192: Update the fulfilled analytics handling in the page’s
analytics-loading flow to inspect the response’s partial_error and warnings
fields. When partial_error is true, surface the available warnings to the user
while still setting the analytics result; preserve the existing full-request
failure handling for rejected responses.
🪄 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: 39ffc658-20e4-4a45-9662-d1b164012b20

📥 Commits

Reviewing files that changed from the base of the PR and between 61bc903 and 15c38da.

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

Comment thread apps/api/internal/handler/analytics.go Outdated
Comment thread apps/api/internal/service/analytics.go Outdated
Comment thread apps/api/internal/service/analytics.go Outdated
Comment thread apps/web/package.json
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.563.0",
"node": "^26.5.0",

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the repository intentionally relies on a locally installed Node binary.
fd -H -t f . | xargs rg -n '"node"|engines|nvm|fnm|volta|asdf|node_modules/\.bin|setup-node'

Repository: Devlaner/devlane

Length of output: 46356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- apps/web/package.json ---'
cat -n apps/web/package.json | sed -n '1,140p'

echo
echo '--- apps/web/package-lock.json (around node dependency) ---'
python3 - <<'PY'
from pathlib import Path
p = Path('apps/web/package-lock.json')
text = p.read_text()
for needle in ['"node": "^26.5.0"', '"node_modules/node"', '"node": "bin/node"', '"bin/node"', '"engines"']:
    idx = text.find(needle)
    print(f'\nNEEDLE: {needle} -> {idx}')
    if idx != -1:
        start = max(0, idx-300)
        end = min(len(text), idx+700)
        snippet = text[start:end]
        print(snippet)
PY

echo
echo '--- repository root package.json (if present) ---'
if [ -f package.json ]; then cat -n package.json | sed -n '1,220p'; else echo 'no root package.json'; fi

echo
echo '--- web package scripts/engines/private ---'
python3 - <<'PY'
import json
from pathlib import Path
data = json.loads(Path('apps/web/package.json').read_text())
for key in ['scripts','engines','dependencies','devDependencies','optionalDependencies','peerDependencies']:
    if key in data:
        print(f'\n[{key}]')
        if isinstance(data[key], dict):
            for k,v in data[key].items():
                if k == 'node' or k.startswith('`@tiptap/`') or k in ('vite','typescript','react','react-dom'):
                    print(f'{k}: {v}')
        else:
            print(data[key])
PY

Repository: Devlaner/devlane

Length of output: 10010


Do not add node as a web-app dependency. This pulls in the node package and its install script, which downloads a local Node binary and can override the system runtime during installs. Declare the supported Node range via engines or the repo’s toolchain/CI instead.

🤖 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/package.json` at line 47, Remove the "node" dependency from
apps/web/package.json and declare the supported Node version through the
package's engines field or the repository's existing toolchain/CI configuration,
preserving the intended version range without installing a local Node binary.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/internal/service/analytics.go (2)

114-147: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

GetProjectAnalytics silently drops priority/assignee/label failures — no error, no log, unlike the workspace twin.

In GetWorkspaceAnalytics, a priority-query failure is fatal (returned as an error) and assignee/label failures are at least logged via s.log.Warn(...). Here, priority/assignee/label errors are swallowed with neither a returned error nor any log call — callers get a 200 OK with silently-empty maps and there is zero observability into why. This is an inconsistent criticality classification between two nearly identical functions.

🔧 Align logging/error semantics with GetWorkspaceAnalytics
 	byPriority := make(map[string]int64)
 	if priorityResults, err := s.store.GetProjectPriorityAnalytics(ctx, projectID); err == nil {
 		for _, r := range priorityResults {
 			byPriority[r.Priority] = r.Count
 		}
+	} else if s.log != nil {
+		s.log.Warn("failed to fetch project priority analytics", "error", err, "project_id", projectID)
 	}
 
 	byAssignee := make(map[string]int64)
 	if assigneeResults, err := s.store.GetProjectAssigneeAnalytics(ctx, projectID); err == nil {
 		for _, r := range assigneeResults {
 			byAssignee[r.Email] = r.Count
 		}
+	} else if s.log != nil {
+		s.log.Warn("failed to fetch project assignee analytics", "error", err, "project_id", projectID)
 	}
 
 	byLabel := make(map[string]int64)
 	if labelResults, err := s.store.GetProjectLabelAnalytics(ctx, projectID); err == nil {
 		for _, r := range labelResults {
 			byLabel[r.Label] = r.Count
 		}
+	} else if s.log != nil {
+		s.log.Warn("failed to fetch project label analytics", "error", err, "project_id", projectID)
 	}
🤖 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/service/analytics.go` around lines 114 - 147, Update
GetProjectAnalytics to match GetWorkspaceAnalytics error semantics: return a
wrapped error when GetProjectPriorityAnalytics fails, and log warnings through
s.log.Warn for GetProjectAssigneeAnalytics and GetProjectLabelAnalytics failures
while preserving successful result population.

106-111: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Define AnalyticsResponse with snake_case JSON tags.

The frontend expects by_state, by_priority, trend, partial_error, and warnings, but apps/api/internal/service/analytics.go returns AnalyticsResponse{ByState, ByPriority, ByAssignee, ByLabel} without the struct definition. Add the Go response type with json:"by_state"/json:"by_priority" tags so /analytics does not emit ByState/ByPriority to the frontend.

Also applies to the project analytics return at lines 149-154.

🤖 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/service/analytics.go` around lines 106 - 111, Define the
AnalyticsResponse type used by the analytics service with snake_case JSON tags
for by_state, by_priority, trend, partial_error, and warnings, and include the
existing assignee and label fields as appropriate. Ensure both the main
analytics return and the project analytics return serialize through this type so
the frontend receives the expected property names instead of Go field names.
🧹 Nitpick comments (3)
apps/api/internal/handler/analytics.go (2)

136-148: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

CSV write/flush errors are discarded in both export handlers.

_ = writer.Write(...) throughout, and writer.Flush()'s subsequent error state is never checked via writer.Error(). A mid-stream write failure would go completely unnoticed.

🔧 Check writer.Error() after Flush
 	writer.Flush()
+	if err := writer.Error(); err != nil {
+		h.Log.Error("csv write error during export", "error", err)
+	}
 }

Also applies to: 186-197

🤖 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 136 - 148, Update both
CSV export handlers, including the shown loop and the corresponding block around
the second export, to stop discarding csv.Writer errors: check each Write result
and check writer.Error() after Flush, propagating or returning the error through
the handler’s existing error path.

34-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused parseParamUUID helper; both project handlers duplicate its logic inline.

parseParamUUID is defined but never called. GetProjectAnalytics and ExportProjectCSV both re-implement the same projectId/projectID fallback + uuid.Parse pattern instead of reusing it.

♻️ Reuse the helper
 func (h *AnalyticsHandler) GetProjectAnalytics(c *gin.Context) {
-	projectParam := c.Param("projectId")
-	if projectParam == "" {
-		projectParam = c.Param("projectID")
-	}
-	projectID, err := uuid.Parse(projectParam)
-	if err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
+	projectID, ok := parseProjectID(c)
+	if !ok {
 		return
 	}

Also applies to: 74-83, 150-159

🤖 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 34 - 46, Use the
existing parseParamUUID helper in GetProjectAnalytics and ExportProjectCSV
instead of duplicating projectId/projectID lookup and UUID parsing inline.
Ensure both handlers pass the appropriate parameter key or preserve the fallback
behavior through the helper, remove the redundant parsing logic, and keep the
existing bad-request responses and early returns.
apps/api/internal/store/analytics.go (1)

125-146: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Export queries load entire result set into memory with no bound.

GetWorkspaceIssuesForExport/GetProjectIssuesForExport Scan the full issue set for a workspace/project with no Limit/pagination/streaming. For large workspaces this directly undercuts the PR's stated scalability goal and risks high memory usage / long-held DB connections on export.

♻️ Consider streaming rows instead of buffering the full slice
-func (s *AnalyticsStore) GetWorkspaceIssuesForExport(ctx context.Context, slug string) ([]model.WorkspaceIssueExport, error) {
-	var exports []model.WorkspaceIssueExport
-	err := s.db.WithContext(ctx).
-		Table("issues").
-		Select("issues.id, issues.name, issues.state, issues.priority").
-		Joins("JOIN workspaces ON issues.workspace_id = workspaces.id").
-		Where("workspaces.slug = ? AND issues.deleted_at IS NULL AND workspaces.deleted_at IS NULL", slug).
-		Scan(&exports).Error
-
-	return exports, err
-}
+// Consider exposing a Rows()-based iterator (or FindInBatches) so the
+// handler can stream CSV rows to the client instead of loading the
+// entire workspace's issues into memory at once.
🤖 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/store/analytics.go` around lines 125 - 146, Update
GetWorkspaceIssuesForExport and GetProjectIssuesForExport so exports do not
unboundedly Scan all matching issues into memory; implement bounded pagination
or streaming row processing while preserving the existing filters and selected
export fields, and ensure resources are properly released.
🤖 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 109-148: Sanitize or escape the user-controlled slug before
interpolating it into the Content-Disposition filename in ExportWorkspaceCSV.
Preserve the existing filename format while ensuring embedded quotes or other
header-sensitive characters cannot break the quoted header value.

In `@apps/api/internal/service/analytics.go`:
- Around line 41-63: Update ensureWorkspaceAccess and ensureProjectAccess to
return underlying errors from GetBySlug, GetByID, and IsMember instead of
classifying them as ErrAnalyticsNotFound or ErrAnalyticsForbidden. Preserve
ErrAnalyticsNotFound only for nil records and ErrAnalyticsForbidden only when
IsMember succeeds with false, so genuine infrastructure failures propagate to
the handler’s 500 path.

In `@apps/api/internal/store/analytics.go`:
- Around line 19-72: Update the workspace-scoped analytics methods
GetWorkspaceStateAnalytics, GetWorkspacePriorityAnalytics,
GetWorkspaceAssigneeAnalytics, and GetWorkspaceLabelAnalytics to join the
relevant projects relation and filter projects.deleted_at IS NULL alongside the
existing issue and workspace soft-delete conditions, ensuring retained issues
from soft-deleted projects are excluded.

---

Outside diff comments:
In `@apps/api/internal/service/analytics.go`:
- Around line 114-147: Update GetProjectAnalytics to match GetWorkspaceAnalytics
error semantics: return a wrapped error when GetProjectPriorityAnalytics fails,
and log warnings through s.log.Warn for GetProjectAssigneeAnalytics and
GetProjectLabelAnalytics failures while preserving successful result population.
- Around line 106-111: Define the AnalyticsResponse type used by the analytics
service with snake_case JSON tags for by_state, by_priority, trend,
partial_error, and warnings, and include the existing assignee and label fields
as appropriate. Ensure both the main analytics return and the project analytics
return serialize through this type so the frontend receives the expected
property names instead of Go field names.

---

Nitpick comments:
In `@apps/api/internal/handler/analytics.go`:
- Around line 136-148: Update both CSV export handlers, including the shown loop
and the corresponding block around the second export, to stop discarding
csv.Writer errors: check each Write result and check writer.Error() after Flush,
propagating or returning the error through the handler’s existing error path.
- Around line 34-46: Use the existing parseParamUUID helper in
GetProjectAnalytics and ExportProjectCSV instead of duplicating
projectId/projectID lookup and UUID parsing inline. Ensure both handlers pass
the appropriate parameter key or preserve the fallback behavior through the
helper, remove the redundant parsing logic, and keep the existing bad-request
responses and early returns.

In `@apps/api/internal/store/analytics.go`:
- Around line 125-146: Update GetWorkspaceIssuesForExport and
GetProjectIssuesForExport so exports do not unboundedly Scan all matching issues
into memory; implement bounded pagination or streaming row processing while
preserving the existing filters and selected export fields, and ensure resources
are properly released.
🪄 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: fb81f93e-939d-4fa0-9e9c-8b05a2f5a379

📥 Commits

Reviewing files that changed from the base of the PR and between 438b4d4 and 68ae41d.

📒 Files selected for processing (5)
  • apps/api/internal/handler/analytics.go
  • apps/api/internal/model/analytics.go
  • apps/api/internal/router/router.go
  • apps/api/internal/service/analytics.go
  • apps/api/internal/store/analytics.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/internal/router/router.go

Comment thread apps/api/internal/handler/analytics.go
Comment thread apps/api/internal/service/analytics.go Outdated
Comment thread apps/api/internal/store/analytics.go
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