-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: Matrix question #2271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Matrix question #2271
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 2 Ignored Deployments
|
|
Thank you for following the naming conventions for pull request titles! 🙏 |
export const DateQuestionSummary = React.memo(({ questionSummary, environmentId }: DateQuestionSummaryProps) => {
// existing code
});
<div
className={`hover:outline-brand-dark m-1 flex h-full w-40 items-center justify-center rounded p-4 text-sm hover:outline ${getAlphaValue(percentage)}`}
>
{percentage}
</div>
<div key={result.value}>
{/* existing code */}
</div>
{timeSince(response.updatedAt)}
const tooltipContent = label ? label : `${Math.round((percentage / 100) * totalResponses)} responses`;In the const handleOnChange = (index: number, type: "row" | "column", e) => {
const newLabel = e.target.value;
const labels = type === "row" ? [...question.rows] : [...question.columns];
// Check for duplicate labels
const isDuplicate = labels.includes(newLabel);
if (isDuplicate) {
toast.error(`Duplicate ${type} labels`);
return; // Return early to prevent adding or updating the label
}
// Update the label at the given index, or add a new label if index is undefined
if (index !== undefined) {
labels[index] = newLabel;
} else {
labels.push(newLabel);
}
if (type === "row") {
updateQuestion(questionIdx, { rows: labels });
} else {
updateQuestion(questionIdx, { columns: labels });
}
};In the const questionTypeComponentMap = {
[TSurveyQuestionType.OpenText]: OpenTextSummary,
[TSurveyQuestionType.MultipleChoiceSingle]: MultipleChoiceSummary,
[TSurveyQuestionType.MultipleChoiceMulti]: MultipleChoiceSummary,
[TSurveyQuestionType.NPS]: NPSSummary,
[TSurveyQuestionType.CTA]: CTASummary,
[TSurveyQuestionType.Rating]: RatingSummary,
[TSurveyQuestionType.Consent]: ConsentSummary,
[TSurveyQuestionType.PictureSelection]: PictureChoiceSummary,
[TSurveyQuestionType.Date]: DateQuestionSummary,
[TSurveyQuestionType.FileUpload]: FileUploadSummary,
[TSurveyQuestionType.Cal]: CalSummary,
[TSurveyQuestionType.Matrix]: MatrixQuestionSummary,
"hiddenField": HiddenFieldsSummary,
};
// ...
summary.map((questionSummary) => {
const Component = questionTypeComponentMap[questionSummary.type];
return Component ? <Component key={questionSummary.question.id} questionSummary={questionSummary} /> : null;
})In the const questionTypeComponentMap = {
[TSurveyQuestionType.OpenText]: OpenTextQuestion,
[TSurveyQuestionType.MultipleChoiceSingle]: MultipleChoiceSingleQuestion,
[TSurveyQuestionType.MultipleChoiceMulti]: MultipleChoiceMultiQuestion,
[TSurveyQuestionType.NPS]: NPSQuestion,
[TSurveyQuestionType.CTA]: CTAQuestion,
[TSurveyQuestionType.Rating]: RatingQuestion,
[TSurveyQuestionType.Consent]: ConsentQuestion,
[TSurveyQuestionType.Date]: DateQuestion,
[TSurveyQuestionType.PictureSelection]: PictureSelectionQuestion,
[TSurveyQuestionType.FileUpload]: FileUploadQuestion,
[TSurveyQuestionType.Cal]: CalQuestion,
[TSurveyQuestionType.Matrix]: MatrixQuestion,
};
// ...
const Component = questionTypeComponentMap[question.type];
return Component ? <Component {...props} /> : null;Where
const formatTextWithSlashes = (text) => {
const regex = /\/(.*?)\\/g;
return text.split(regex).reduce((acc, part, index) => {
if (index % 2 !== 0) {
acc.push(
<span key={index} className="mx-1 rounded-md bg-slate-100 p-1 px-2 text-xs">
{part}
</span>
);
} else {
acc.push(part);
}
return acc;
}, []);
};
const updateEmptyNextButtonLabels = (labelValue: string) => {
for (let index = 0; index < localSurvey.questions.length - 1; index++) {
const q = localSurvey.questions[index];
if (!q.buttonLabel || q.buttonLabel?.trim() === "") {
updateQuestion(index, { buttonLabel: labelValue });
}
}
};
if (
question.type === TSurveyQuestionType.MultipleChoiceSingle ||
question.type === TSurveyQuestionType.MultipleChoiceMulti
) {
const choiceLabels = new Set(question.choices.map((choice) => choice.label.trim().toLowerCase()));
if (choiceLabels.has("") || choiceLabels.size !== question.choices.length) {
toast.error("You have empty or duplicate choices.");
return false;
}
}
const saveSurveyAction = async (shouldNavigateBack = false) => {
if (!validateSurvey(localSurvey)) {
setIsSurveySaving(false);
return;
}
// rest of the function...
};
const handleSurveyPublish = async () => {
if (!validateSurvey(localSurvey)) {
setIsSurveyPublishing(false);
return;
}
// rest of the function...
};The new code added in the PR seems to be handling a new case for
Here are the code suggestions: // Add key prop in map functions
{firstQuestion.columns.map((column, index) => {
return <th key={index} className="max-w-40 break-words px-4 py-2 text-gray-800">{column}</th>;
})}
{firstQuestion.rows.map((row, rowIndex) => {
return (
<tr key={rowIndex} className={`${rowIndex % 2 === 0 ? "bg-gray-100" : ""}`}>
<td className="max-w-40 break-words px-4 py-2">{row}</td>
{firstQuestion.columns.map((_, colIndex) => {
return (
<td key={colIndex} className="px-4 py-2 text-gray-800">
<Section className="h-4 w-4 rounded-full bg-white p-2 outline"></Section>
</td>
);
})}
</tr>
);
})}
// Use td tag instead of th in tbody
{firstQuestion.columns.map(() => {
return (
<td className="px-4 py-2 text-gray-800">
<Section className="h-4 w-4 rounded-full bg-white p-2 outline"></Section>
</td>
);
})}These changes will improve the semantic correctness of your HTML and help React optimize re-rendering. if (!preset.rows.length || !preset.columns.length) {
throw new Error("Rows and columns cannot be empty for Matrix question type");
}In the else if (typeof responseData === "object") {
return JSON.stringify(responseData);
}In the responses.push(responseValue !== undefined ? processResponseData(responseValue) : "");In the if (!filterType.filterValue || !filterType.filterComboBoxValue) {
throw new Error("Filter value and combo box value cannot be empty for Matrix question type");
}The code in this PR is generally well-written and follows good practices. However, there are a few areas where improvements can be made:
const { finished, createdAt, tags, personAttributes, data } = filterCriteria || {};
import { processResponseData } from './responseProcessing';
const questionHandlers = {
[TSurveyQuestionType.OpenText]: handleOpenTextQuestion,
[TSurveyQuestionType.MultipleChoiceSingle]: handleMultipleChoiceQuestion,
// ...
};
function handleOpenTextQuestion(question, responses) {
// ...
}
function handleMultipleChoiceQuestion(question, responses) {
// ...
}
// ...
survey.questions.forEach((question) => {
const handler = questionHandlers[question.type];
if (handler) {
handler(question, responses);
}
});
export function calculateTtcTotal(ttc: TResponseTtc) {
return {
...ttc,
_total: Object.values(ttc).reduce((acc: number, val: number) => acc + val, 0),
};
}
import { convertFloatTo2Decimal } from './numberProcessing'; |
ShubhamPalriwala
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @Dhruwang this looks amazing! 🔥 Just saw the functionality & the use cases are promising. Well built even on the responsiveness! Once the Jumping is ready, it'd be even nice to test 😉
I ran into a few small bugs during testing, could you please take a look at them?
Bugs:
-
Can we follow the same order of the rows that we do during question creation:

In the response it currently looks like:

-
In the survey editor, when adding a column, on pressing enter (expecting to enter a new column), i enter a new row:

-
Same for rows as well:

Can we use tooltips here as mentioned in the issue & limit with max heights & widhts? -
Even if the question is optional, it still does not let me move forward without filling the grid in:

...s/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx
Outdated
Show resolved
Hide resolved
...s/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx
Outdated
Show resolved
Hide resolved
...pp)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/emailTemplate.tsx
Outdated
Show resolved
Hide resolved
...(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx
Outdated
Show resolved
Hide resolved
| <OpenTextQuestion | ||
| question={question} | ||
| value={value} | ||
| value={typeof value === "string" ? value : ""} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not seem great to have type checks everywhere? Can you see if you can handle this in a much cleaner way?
Last option is to have a utility function that does it at one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't able to come up with a good solution here, do you have anything on mind?🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try asking GPT! It should help I think!
|
Hey @ShubhamPalriwala , have updated the PR with mentioned changes 😊 Regarding the issue of the UI breaking with lengthy row and column names, we've chosen to implement tooltips exclusively on the summary page. However, for the question page, our approach is to display the entire label and offer users the necessary scrolling options to view these labels in full. |
ShubhamPalriwala
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Looks pretty solid to me! Cannot face any of the previous bugs anymore 💪🏼
Just one small fix:

In the above, I see a cursor pointer on the heatmap when hovered? is it supposed to do something on click? is this behaviour intended?
Apart from these, just 2-3 small code changes, rest all looks good from my end 🚀
ShubhamPalriwala
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM ✅ 🚀
…ricks into matrix-question
|
Hey @jobenjada , have fixed the mentioned issues Fixed issue - 4 Screen.Recording.2024-03-29.at.10.31.15.AM.mp4Fixed issue - 7 You can also adjust the value for it in function |
jobenjada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM



















What does this PR do?
Fixes 1185
Preview:

Matrix question summary:

Respones Card

Embed survey

Response received email

CSV download

Integration

How should this be tested?
Sections provided above
Checklist
Required
pnpm buildconsole.logsgit pull origin mainAppreciated