Skip to content
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
55 changes: 14 additions & 41 deletions apps/api/plane/app/views/analytic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
Module,
)

from plane.utils.analytics_plot import build_graph_plot
from plane.utils.analytics_plot import build_graph_plot, VALID_ANALYTICS_FIELDS, VALID_YAXIS
from plane.utils.issue_filters import issue_filters
from plane.app.permissions import allow_permission, ROLE

Expand All @@ -41,32 +41,15 @@ def get(self, request, slug):
y_axis = request.GET.get("y_axis", False)
segment = request.GET.get("segment", False)

valid_xaxis_segment = [
"state_id",
"state__group",
"labels__id",
"assignees__id",
"estimate_point__value",
"issue_cycle__cycle_id",
"issue_module__module_id",
"priority",
"start_date",
"target_date",
"created_at",
"completed_at",
]

valid_yaxis = ["issue_count", "estimate"]

# Check for x-axis and y-axis as thery are required parameters
if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis:
if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
return Response(
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
status=status.HTTP_400_BAD_REQUEST,
)

# If segment is present it cannot be same as x-axis
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
return Response(
{"error": "Both segment and x axis cannot be same and segment should be valid"},
status=status.HTTP_400_BAD_REQUEST,
Expand Down Expand Up @@ -214,13 +197,20 @@ def get(self, request, slug, analytic_id):
x_axis = analytic_view.query_dict.get("x_axis", False)
y_axis = analytic_view.query_dict.get("y_axis", False)

if not x_axis or not y_axis:
if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
return Response(
{"error": "x-axis and y-axis dimensions are required"},
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
status=status.HTTP_400_BAD_REQUEST,
)

segment = request.GET.get("segment", False)

if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
return Response(
{"error": "Both segment and x axis cannot be same and segment should be valid"},
Comment on lines 206 to +210
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

build_graph_plot() now raises ValueError for invalid x_axis/y_axis, but this endpoint takes those values from analytic_view.query_dict (not the request) and doesn’t validate/catch errors before calling build_graph_plot(). A malformed/legacy/malicious saved analytic could now produce a 500. Consider validating x_axis/y_axis against the allowlists here and/or catching ValueError and returning a 400 response.

Copilot uses AI. Check for mistakes.
status=status.HTTP_400_BAD_REQUEST,
)

distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment)
total_issues = queryset.count()
return Response(
Expand All @@ -236,32 +226,15 @@ def post(self, request, slug):
y_axis = request.data.get("y_axis", False)
segment = request.data.get("segment", False)

valid_xaxis_segment = [
"state_id",
"state__group",
"labels__id",
"assignees__id",
"estimate_point",
"issue_cycle__cycle_id",
"issue_module__module_id",
"priority",
"start_date",
"target_date",
"created_at",
"completed_at",
]

valid_yaxis = ["issue_count", "estimate"]

# Check for x-axis and y-axis as thery are required parameters
if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis:
if not x_axis or not y_axis or x_axis not in VALID_ANALYTICS_FIELDS or y_axis not in VALID_YAXIS:
return Response(
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
status=status.HTTP_400_BAD_REQUEST,
)
Comment on lines 229 to 234
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

PR description says ExportAnalyticsEndpoint allowlist was fixed to use estimate_point instead of estimate_point__value, but VALID_ANALYTICS_FIELDS (used here for validation) includes estimate_point__value (and not estimate_point). Please reconcile the PR description/test plan with the actual intended accepted field name to avoid confusion for reviewers and release notes.

Copilot uses AI. Check for mistakes.

# If segment is present it cannot be same as x-axis
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
if segment and (segment not in VALID_ANALYTICS_FIELDS or x_axis == segment):
return Response(
{"error": "Both segment and x axis cannot be same and segment should be valid"},
status=status.HTTP_400_BAD_REQUEST,
Expand Down
26 changes: 26 additions & 0 deletions apps/api/plane/utils/analytics_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@
# Module imports
from plane.db.models import Issue, Project

VALID_ANALYTICS_FIELDS = [
"state_id",
"state__group",
"labels__id",
"assignees__id",
"estimate_point__value",
"issue_cycle__cycle_id",
"issue_module__module_id",
"priority",
"start_date",
"target_date",
"created_at",
"completed_at",
]

VALID_YAXIS = ["issue_count", "estimate"]


def annotate_with_monthly_dimension(queryset, field_name, attribute):
# Get the year and the months
Expand All @@ -34,6 +51,8 @@ def annotate_with_monthly_dimension(queryset, field_name, attribute):


def extract_axis(queryset, x_axis):
if x_axis not in VALID_ANALYTICS_FIELDS:
raise ValueError(f"Invalid x_axis value: {x_axis}")
# Format the dimension when the axis is in date
if x_axis in ["created_at", "start_date", "target_date", "completed_at"]:
queryset = annotate_with_monthly_dimension(queryset, x_axis, "dimension")
Expand All @@ -52,6 +71,13 @@ def sort_data(data, temp_axis):


def build_graph_plot(queryset, x_axis, y_axis, segment=None):
if x_axis not in VALID_ANALYTICS_FIELDS:
raise ValueError(f"Invalid x_axis value: {x_axis}")
if y_axis not in VALID_YAXIS:
raise ValueError(f"Invalid y_axis value: {y_axis}")
if segment and segment not in VALID_ANALYTICS_FIELDS:
raise ValueError(f"Invalid segment value: {segment}")

# temp x_axis
temp_axis = x_axis
# Extract the x_axis and queryset
Expand Down
Loading