Remove sentiment, emotion, and the TopicsUnnested join from the Cube
schema, web registry, AI prompt, audit-allowlist, and i18n keys — these
required metadata enrichment that is no longer planned. Tests swap the
removed string dimensions for sourceType to keep coverage intact.
Add csatDissatisfiedCount and csatNeutralCount so dashboards can render
the standard satisfied/neutral/dissatisfied distribution alongside the
existing top-2-box metric.
Expose field_label, field_group_label, language, value_boolean,
value_date, created_at, and updated_at as Cube dimensions. fieldLabel
and fieldGroupLabel unlock "group by question" and the matrix/ranking
aggregations enabled by the recent composite-question PR; the others
round out coverage of the underlying feedback_records columns. Extend
FieldDefinition with a boolean type and matching filter operators.
Adds CSAT and CES measures, plus a couple of universal cross-type measures,
to the FeedbackRecords Cube. Also fixes correctness bugs in the existing NPS
measures and constrains the AI chart-query output to known measure / dimension
ids.
Measures added
- csatScore: % of answered CSAT responses rated 4 or 5 (top-2-box on 1-5).
- csatAverage: AVG of answered CSAT ratings.
- csatSatisfiedCount: count where field_type='csat' AND value_number >= 4.
- csatCount: count of answered CSAT responses.
- cesAverage: AVG of answered CES ratings (1-5 or 1-7 depending on the question).
- cesCount: count of answered CES responses.
- uniqueRespondents: countDistinct(user_id).
- uniqueResponses: countDistinct(submission_id).
- npsAverage: AVG of answered NPS ratings (replaces the old `averageScore`
measure, which silently averaged every numeric value across all field types).
Bug fixes (existing NPS measures)
- promoterCount / passiveCount / detractorCount / npsScore now filter
field_type = 'nps'. Before, any numeric value in the band was counted
(so a CSAT 5/5 was lumped in as a detractor).
- npsScore now divides by *answered* NPS responses only (value_number IS NOT
NULL); dismissed/abandoned NPS records no longer drag the score toward 0.
- npsScore returns NULL when no answered NPS responses exist instead of the
literal 0, so empty-data days render as gaps rather than a misleading
flat-zero line.
- Same NULL-safe denominator fix applied to csatScore.
- csatCount / cesCount now exclude dismissed (value_number IS NULL) responses
so they match the score denominators.
Dimensions
- Renamed `npsValue` -> `valueNumber` (the underlying value_number column
stores values for NPS, CSAT, CES, rating, and number field types).
- New `valueText` dimension for grouping by categorical / open-text answers.
AI chart generation
- ZGenerateAIQueryResponse now enum-constrains `measures`, `dimensions`,
`timeDimensions.dimension`, and `filters.member` against the known ids
derived from FEEDBACK_FIELDS. Vercel AI SDK + Gemini structured outputs
enforce these at decoding time, so the model can no longer return invalid
or hallucinated measure names.
- generateText now pins temperature: 0. Same prompt produces the same query.
Breaking changes
- Charts that referenced `FeedbackRecords.averageScore` need to switch to
`FeedbackRecords.npsAverage`.
- Charts that referenced `FeedbackRecords.npsValue` need to switch to
`FeedbackRecords.valueNumber`.
Pre-GA on epic/v5; no production charts are expected to be impacted yet.
Same pattern as CartesianChart's ChartTooltipRow — a module-level
component that calls useTranslation internally, plus a thin formatter
function passed to ChartTooltipContent. Resolves the nested-component
warning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch from the native browser tooltip to the in-app inline error pattern:
red Label, isInvalid Input border, and helper text below the field. The
onInvalid handler suppresses the default tooltip and scrolls + focuses the
field via event.currentTarget — no ref needed. Typing clears the error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Browsers default to "Please fill in this field"; use the existing
workspace.analysis.charts.please_enter_chart_name key so the message
matches the toast we surface elsewhere. Clear custom validity on change
so the form unblocks once the user types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the imperative scrollIntoView + focus on Save click with a tiny
<form> wrapping the required Input. The Save button becomes type=submit
with form="create-chart-form", so the browser handles scroll, focus, and
the "Please fill in this field" tooltip natively when the name is empty.
ChartDialogFooter now accepts either onSaveClick (button mode) or formId
(submit-button mode). All sibling buttons in the footer are explicitly
type="button" so they don't accidentally submit the form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking Save Chart with the name field empty fired a toast but left the
modal scrolled wherever the user was (e.g. on the chart preview at the
bottom), so they couldn't see what was wrong. Scroll the name field into
view and focus it before delegating to the existing save handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fractional-tick bar/line/area scenario isn't the bug we're shipping in
this PR. Keep only the pie tooltip spacing/formatting change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returning [value, name] from the tooltip formatter rendered as two adjacent text nodes ("3Nps"). Return a fragment with an explicit space so it reads "3 Nps".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ENG-914
When a chart's measures are all whole numbers (e.g. COUNT grouped by a
dimension), Recharts was auto-generating fractional y-axis ticks like
0.5, 1.5, 2 — nonsensical for a discrete count. The pie tooltip also
rendered values via raw String() with no thousand separators.
- Add allValuesAreIntegers() + formatYAxisTick() to chart-utils.
- CartesianChart now passes allowDecimals={false} and a number-aware
tickFormatter to YAxis when every measure value is an integer.
- ChartRenderer's pie tooltip routes the value through formatCellValue,
matching the Cartesian tooltip behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- FormError ignored its children when an error was set, rendering the raw
zod message instead of the translated text passed in. Prefer explicit
children, fall back to the field error message.
- Use .at(-1) in getTranslatedFeedbackDirectoryError per SonarQube S7755.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Johannes on ENG-893: zero-workspace creation is acceptable — admins
move workspaces between directories and forcing at least one is an
arbitrary limitation. The unformatted-error fix
(getTranslatedFeedbackDirectoryError prefix stripping) is retained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ENG-893
Why: A feedback directory with zero workspaces is useless — it groups
nothing. The create path allowed it, and any validation error that did
fire surfaced as a raw "field: CODE" string in the toast.
How:
- createFeedbackDirectory now rejects empty/missing workspaceIds with
DIRECTORY_WORKSPACES_REQUIRED before touching the database.
- getTranslatedFeedbackDirectoryError strips the "<field>: " prefix that
next-safe-action prepends to validation errors, so machine codes always
map to a translated message.
- Add the new translation key across all 15 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests now expect validation failures when CUBEJS_API_URL, CUBEJS_API_SECRET,
or HUB_API_KEY are missing, and all test env helpers provide HUB_API_KEY.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Makes Cube env vars mandatory in env.ts (per PR #7913) and adds them
as Docker build secrets with fallback values, following the same pattern
as DATABASE_URL, REDIS_URL, and HUB_API_URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HUB_API_URL is required by the Zod env validation at build time but was
not provided as a Docker secret, causing the release build to fail.
Adds HUB_API_URL with a dummy fallback (http://localhost:4000) to the
build pipeline, following the same pattern as DATABASE_URL/REDIS_URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hub 0.3.0 renames the `user_identifier` API field to `user_id` (breaking
change). This commit bumps the Hub Docker image, upgrades the
@formbricks/hub TypeScript SDK from 0.4.3 to 0.5.0, and renames every
`user_identifier` reference in Zod schemas, server actions, transform
pipeline, form components, CubeJS schema, connector types, and seed data
to match the new API contract.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>