Skip to content

Commit 352736f

Browse files
authored
fix(results): check data correctly to avoid crashes when visualizing incomplete ratings (#645)
1 parent 4a054d9 commit 352736f

File tree

6 files changed

+162
-39
lines changed

6 files changed

+162
-39
lines changed

cypress/e2e/builder/main.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('Builder View with admin rights, no settings', () => {
4141
cy.get(buildDataCy(RESPONSE_COLLECTION_VIEW_CY)).should('exist');
4242
});
4343

44-
it('create some responses, and hit next round', () => {
44+
it('create some responses, and hit next step', () => {
4545
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.PLAY_BUTTON)).should(
4646
'have.lengthOf',
4747
1,

cypress/e2e/player/main.cy.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import { Context, PermissionLevel } from '@graasp/sdk';
33
import {
44
ADMIN_PANEL_CY,
55
DETAILS_INSTRUCTIONS_CY,
6+
LIKERT_RATING_CY,
67
ORCHESTRATION_BAR_CY,
78
PROMPTS_CY,
89
PROPOSE_NEW_RESPONSE_BTN_CY,
910
RESPONSE_COLLECTION_VIEW_CY,
11+
RESPONSE_CY,
1012
RESPONSE_EVALUATION_VIEW_CY,
1113
RESPONSE_RESULTS_VIEW_CY,
14+
SUBMIT_RESPONSE_BTN_CY,
1215
TITLE_INSTRUCTIONS_CY,
1316
buildDataCy,
1417
} from '@/config/selectors';
@@ -22,6 +25,7 @@ import {
2225
ALL_SETTINGS,
2326
ALL_SETTINGS_OBJECT,
2427
SETTINGS_WITH_ASSISTANT,
28+
SETTINGS_WITH_RATINGS,
2529
} from '../../fixtures/appSettings';
2630
import { MEMBERS } from '../../fixtures/members';
2731

@@ -108,7 +112,7 @@ describe('Player with read rights, configured with one assistant and no data.',
108112
});
109113

110114
it('goes through all the steps.', () => {
111-
const MEAN_WAITING_TIME = 6000;
115+
const MEAN_WAITING_TIME = 4000;
112116
// Propose a new idea, then go to next step
113117
cy.get(buildDataCy(ADMIN_PANEL_CY)).should('not.exist');
114118

@@ -128,15 +132,75 @@ describe('Player with read rights, configured with one assistant and no data.',
128132
// eslint-disable-next-line cypress/no-unnecessary-waiting
129133
cy.wait(MEAN_WAITING_TIME);
130134

131-
// cy.get(buildDataCy(RESPONSE_CY))
132-
// .first()
133-
// .within(() => {
134-
// cy.get(buildDataCy(LIKERT_RATING_CY))
135-
// .first()
136-
// .within(() => {
137-
// cy.get('input[value=5]').click({ force: true });
138-
// });
139-
// });
135+
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.NEXT_STEP_BTN)).click();
136+
137+
cy.get(buildDataCy(RESPONSE_RESULTS_VIEW_CY)).should('exist');
138+
});
139+
});
140+
141+
describe('Player with read rights, configured to rate ideas.', () => {
142+
beforeEach(() => {
143+
cy.setUpApi(
144+
{
145+
appSettings: SETTINGS_WITH_RATINGS,
146+
appData: [],
147+
},
148+
{
149+
context: Context.Player,
150+
permission: PermissionLevel.Read,
151+
accountId: MEMBERS.ANNA.id,
152+
},
153+
);
154+
cy.visit('/');
155+
});
156+
157+
it('types a few ideas and rate them.', () => {
158+
const MEAN_WAITING_TIME = 4000;
159+
// Propose a new idea, then go to next step
160+
cy.get(buildDataCy(ADMIN_PANEL_CY)).should('not.exist');
161+
162+
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.PLAY_BUTTON)).click();
163+
164+
const newIdeas = ['Testing this software', "I don't know.", 'Sleep...'];
165+
166+
cy.get(buildDataCy(RESPONSE_COLLECTION_VIEW_CY)).within(() => {
167+
newIdeas.forEach((idea) => {
168+
cy.get(buildDataCy(PROPOSE_NEW_RESPONSE_BTN_CY)).click();
169+
cy.get('#input-response').type('a');
170+
cy.get('#input-response').type('{backspace}');
171+
cy.get('#input-response').should('be.enabled');
172+
cy.get('#input-response').type(idea, { delay: 20 });
173+
cy.get(buildDataCy(SUBMIT_RESPONSE_BTN_CY)).click();
174+
});
175+
});
176+
177+
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.NEXT_STEP_BTN)).click();
178+
// eslint-disable-next-line cypress/no-unnecessary-waiting
179+
cy.wait(MEAN_WAITING_TIME);
180+
181+
cy.get(buildDataCy(RESPONSE_EVALUATION_VIEW_CY)).should('exist');
182+
183+
cy.get(buildDataCy(RESPONSE_CY))
184+
.first()
185+
.within(() => {
186+
cy.get(buildDataCy(LIKERT_RATING_CY)).should('have.length', 2);
187+
cy.get(buildDataCy(LIKERT_RATING_CY))
188+
.first()
189+
.within(() => {
190+
cy.get('input[value=5]').click({ force: true });
191+
});
192+
});
193+
194+
cy.get(buildDataCy(RESPONSE_CY))
195+
.last()
196+
.within(() => {
197+
cy.get(buildDataCy(LIKERT_RATING_CY)).should('have.length', 2);
198+
cy.get(buildDataCy(LIKERT_RATING_CY))
199+
.last()
200+
.within(() => {
201+
cy.get('input[value=2]').click({ force: true });
202+
});
203+
});
140204

141205
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.NEXT_STEP_BTN)).click();
142206

cypress/fixtures/appSettings.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { AppSetting } from '@graasp/sdk';
22

3+
import cloneDeep from 'lodash.clonedeep';
4+
35
import { AllSettingsType } from '@/config/appSettingsType';
4-
import { DEFAULT_SYSTEM_PROMPT } from '@/config/prompts';
6+
import { AssistantType } from '@/interfaces/assistant';
57
import { EvaluationType } from '@/interfaces/evaluation';
68
import {
79
ActivityType,
@@ -81,11 +83,9 @@ export const ALL_SETTINGS_OBJECT: AllSettingsType = {
8183
},
8284
],
8385
reformulateResponses: false,
86+
numberOfParticipantsResponsesTriggeringResponsesGeneration: 0,
8487
},
8588
notParticipating: { ids: [] },
86-
chatbot: {
87-
systemPrompt: DEFAULT_SYSTEM_PROMPT,
88-
},
8989
assistants: {
9090
assistants: [],
9191
},
@@ -99,19 +99,20 @@ export const ALL_SETTINGS = Object.entries(ALL_SETTINGS_OBJECT).map(
9999
([key, value]) => newSettingFactory(key, value),
100100
);
101101

102-
const SETTINGS_WITH_ASSISTANT_OBJECT = ALL_SETTINGS_OBJECT;
102+
const SETTINGS_WITH_ASSISTANT_OBJECT = cloneDeep(ALL_SETTINGS_OBJECT);
103103

104104
SETTINGS_WITH_ASSISTANT_OBJECT.assistants.assistants = [
105105
{
106106
id: 'assistant1',
107107
name: 'GraaspBot',
108-
message: [
108+
configuration: [
109109
{
110110
role: 'system',
111111
content:
112112
'You are a helpful assistant. You always give your most creative ideas.',
113113
},
114114
],
115+
type: AssistantType.LLM,
115116
},
116117
];
117118
SETTINGS_WITH_ASSISTANT_OBJECT.activity.steps = [
@@ -144,3 +145,48 @@ SETTINGS_WITH_ASSISTANT_OBJECT.activity.steps = [
144145
export const SETTINGS_WITH_ASSISTANT = Object.entries(
145146
SETTINGS_WITH_ASSISTANT_OBJECT,
146147
).map(([key, value]) => newSettingFactory(key, value));
148+
149+
const SETTINGS_WITH_RATINGS_OBJECT = cloneDeep(ALL_SETTINGS_OBJECT);
150+
151+
SETTINGS_WITH_RATINGS_OBJECT.activity.steps = [
152+
{
153+
type: ActivityType.Collection,
154+
round: 0,
155+
time: 1,
156+
},
157+
{
158+
type: ActivityType.Evaluation,
159+
round: 1,
160+
time: 1,
161+
evaluationType: EvaluationType.Rate,
162+
evaluationParameters: {
163+
ratings: [
164+
{
165+
name: 'Usefulness',
166+
description: 'How useful is the response',
167+
maxLabel: 'Useful',
168+
minLabel: 'Useless',
169+
levels: 5,
170+
},
171+
{
172+
name: 'Novelty',
173+
description: 'How novel is the response',
174+
maxLabel: 'Novel',
175+
minLabel: 'Common',
176+
levels: 5,
177+
},
178+
],
179+
ratingsName: 'Usefulness and novelty',
180+
},
181+
},
182+
{
183+
type: ActivityType.Results,
184+
round: 1,
185+
time: 240,
186+
resultsType: EvaluationType.Rate,
187+
},
188+
];
189+
190+
export const SETTINGS_WITH_RATINGS = Object.entries(
191+
SETTINGS_WITH_RATINGS_OBJECT,
192+
).map(([key, value]) => newSettingFactory(key, value));

src/langs/en/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
"VOTES_LEFT": "{{availableVotes}} votes left out of {{maxNumberOfVotes}}"
187187
},
188188
"NO_VISUALIZATION": "No visualization is available for this type of evaluation.",
189+
"NO_DATA_FOR_RATING": "No data for {{ratingName}}",
189190
"USEFULNESS_NOVELTY": {
190191
"COMMON": "Common",
191192
"NOVEL": "Novel",

src/modules/common/response/visualization/RatingsVisualization.tsx

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { FC, useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
23

3-
import CircularProgress from '@mui/material/CircularProgress';
4+
import { LinearProgress, Typography } from '@mui/material';
45
import Stack from '@mui/material/Stack';
56

67
import { RatingData } from '@/config/appDataTypes';
8+
import { TRANSLATIONS_NS } from '@/config/i18n';
79
import { useRatingsContext } from '@/modules/context/RatingsContext';
810

911
import CircularIndicator from './indicators/CircularIndicator';
@@ -15,6 +17,7 @@ interface RatingsVisualizationProps {
1517
const RatingsVisualization: FC<RatingsVisualizationProps> = ({
1618
responseId,
1719
}): JSX.Element => {
20+
const { t } = useTranslation(TRANSLATIONS_NS, { keyPrefix: 'EVALUATION' });
1821
const {
1922
ratings: ratingsDef,
2023
getRatingsStatsForResponse,
@@ -29,11 +32,6 @@ const RatingsVisualization: FC<RatingsVisualizationProps> = ({
2932
getRatingsStatsForResponse(responseId).then((d) => setRatings(d));
3033
}, [getRatingsStatsForResponse, responseId]);
3134

32-
if (typeof ratingsDef === 'undefined' || typeof ratings === 'undefined') {
33-
// TODO: Make that look good.
34-
return <CircularProgress />;
35-
}
36-
3735
const nbrRatings = ratingsDef?.length ?? 0;
3836

3937
return (
@@ -44,22 +42,33 @@ const RatingsVisualization: FC<RatingsVisualizationProps> = ({
4442
justifyContent="center"
4543
m={2}
4644
>
47-
{ratingsDef.map((singleRatingDefinition, index) => {
48-
const { name } = singleRatingDefinition;
49-
if (ratings) {
50-
const result = ratings[index];
51-
return (
52-
<CircularIndicator
53-
key={index}
54-
value={result.value}
55-
thresholds={ratingsThresholds}
56-
label={name}
57-
width={`${100 / nbrRatings}%`}
58-
/>
59-
);
60-
}
61-
return <CircularProgress key={index} />;
62-
})}
45+
{typeof ratingsDef === 'undefined' || typeof ratings === 'undefined' ? (
46+
<LinearProgress />
47+
) : (
48+
ratingsDef.map((singleRatingDefinition, index) => {
49+
const { name } = singleRatingDefinition;
50+
if (ratings) {
51+
const result = ratings[index];
52+
if (typeof result === 'undefined') {
53+
return (
54+
<Typography key={index} variant="caption">
55+
{t('NO_DATA_FOR_RATING', { ratingName: name })}
56+
</Typography>
57+
);
58+
}
59+
return (
60+
<CircularIndicator
61+
key={index}
62+
value={result.value}
63+
thresholds={ratingsThresholds}
64+
label={name}
65+
width={`${100 / nbrRatings}%`}
66+
/>
67+
);
68+
}
69+
return <LinearProgress key={index} />;
70+
})
71+
)}
6372
</Stack>
6473
);
6574
};

src/modules/context/RatingsContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ export const RatingsProvider: FC<RatingsContextProps> = ({
165165
const extractedRatings = ratingsForResponse.map(
166166
({ data }) => data.ratings,
167167
);
168+
if (extractedRatings.length === 0) {
169+
return undefined;
170+
}
168171

169172
const initialVal = extractedRatings[0].map((r) => ({
170173
...r,

0 commit comments

Comments
 (0)