Skip to content

Commit

Permalink
fix(results): check data correctly to avoid crashes when visualizing …
Browse files Browse the repository at this point in the history
…incomplete ratings (#645)
  • Loading branch information
swouf authored Nov 13, 2024
1 parent 4a054d9 commit 352736f
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 39 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/builder/main.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Builder View with admin rights, no settings', () => {
cy.get(buildDataCy(RESPONSE_COLLECTION_VIEW_CY)).should('exist');
});

it('create some responses, and hit next round', () => {
it('create some responses, and hit next step', () => {
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.PLAY_BUTTON)).should(
'have.lengthOf',
1,
Expand Down
84 changes: 74 additions & 10 deletions cypress/e2e/player/main.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { Context, PermissionLevel } from '@graasp/sdk';
import {
ADMIN_PANEL_CY,
DETAILS_INSTRUCTIONS_CY,
LIKERT_RATING_CY,
ORCHESTRATION_BAR_CY,
PROMPTS_CY,
PROPOSE_NEW_RESPONSE_BTN_CY,
RESPONSE_COLLECTION_VIEW_CY,
RESPONSE_CY,
RESPONSE_EVALUATION_VIEW_CY,
RESPONSE_RESULTS_VIEW_CY,
SUBMIT_RESPONSE_BTN_CY,
TITLE_INSTRUCTIONS_CY,
buildDataCy,
} from '@/config/selectors';
Expand All @@ -22,6 +25,7 @@ import {
ALL_SETTINGS,
ALL_SETTINGS_OBJECT,
SETTINGS_WITH_ASSISTANT,
SETTINGS_WITH_RATINGS,
} from '../../fixtures/appSettings';
import { MEMBERS } from '../../fixtures/members';

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

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

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

// cy.get(buildDataCy(RESPONSE_CY))
// .first()
// .within(() => {
// cy.get(buildDataCy(LIKERT_RATING_CY))
// .first()
// .within(() => {
// cy.get('input[value=5]').click({ force: true });
// });
// });
cy.get(buildDataCy(ORCHESTRATION_BAR_CY.NEXT_STEP_BTN)).click();

cy.get(buildDataCy(RESPONSE_RESULTS_VIEW_CY)).should('exist');
});
});

describe('Player with read rights, configured to rate ideas.', () => {
beforeEach(() => {
cy.setUpApi(
{
appSettings: SETTINGS_WITH_RATINGS,
appData: [],
},
{
context: Context.Player,
permission: PermissionLevel.Read,
accountId: MEMBERS.ANNA.id,
},
);
cy.visit('/');
});

it('types a few ideas and rate them.', () => {
const MEAN_WAITING_TIME = 4000;
// Propose a new idea, then go to next step
cy.get(buildDataCy(ADMIN_PANEL_CY)).should('not.exist');

cy.get(buildDataCy(ORCHESTRATION_BAR_CY.PLAY_BUTTON)).click();

const newIdeas = ['Testing this software', "I don't know.", 'Sleep...'];

cy.get(buildDataCy(RESPONSE_COLLECTION_VIEW_CY)).within(() => {
newIdeas.forEach((idea) => {
cy.get(buildDataCy(PROPOSE_NEW_RESPONSE_BTN_CY)).click();
cy.get('#input-response').type('a');
cy.get('#input-response').type('{backspace}');
cy.get('#input-response').should('be.enabled');
cy.get('#input-response').type(idea, { delay: 20 });
cy.get(buildDataCy(SUBMIT_RESPONSE_BTN_CY)).click();
});
});

cy.get(buildDataCy(ORCHESTRATION_BAR_CY.NEXT_STEP_BTN)).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(MEAN_WAITING_TIME);

cy.get(buildDataCy(RESPONSE_EVALUATION_VIEW_CY)).should('exist');

cy.get(buildDataCy(RESPONSE_CY))
.first()
.within(() => {
cy.get(buildDataCy(LIKERT_RATING_CY)).should('have.length', 2);
cy.get(buildDataCy(LIKERT_RATING_CY))
.first()
.within(() => {
cy.get('input[value=5]').click({ force: true });
});
});

cy.get(buildDataCy(RESPONSE_CY))
.last()
.within(() => {
cy.get(buildDataCy(LIKERT_RATING_CY)).should('have.length', 2);
cy.get(buildDataCy(LIKERT_RATING_CY))
.last()
.within(() => {
cy.get('input[value=2]').click({ force: true });
});
});

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

Expand Down
58 changes: 52 additions & 6 deletions cypress/fixtures/appSettings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AppSetting } from '@graasp/sdk';

import cloneDeep from 'lodash.clonedeep';

import { AllSettingsType } from '@/config/appSettingsType';
import { DEFAULT_SYSTEM_PROMPT } from '@/config/prompts';
import { AssistantType } from '@/interfaces/assistant';
import { EvaluationType } from '@/interfaces/evaluation';
import {
ActivityType,
Expand Down Expand Up @@ -81,11 +83,9 @@ export const ALL_SETTINGS_OBJECT: AllSettingsType = {
},
],
reformulateResponses: false,
numberOfParticipantsResponsesTriggeringResponsesGeneration: 0,
},
notParticipating: { ids: [] },
chatbot: {
systemPrompt: DEFAULT_SYSTEM_PROMPT,
},
assistants: {
assistants: [],
},
Expand All @@ -99,19 +99,20 @@ export const ALL_SETTINGS = Object.entries(ALL_SETTINGS_OBJECT).map(
([key, value]) => newSettingFactory(key, value),
);

const SETTINGS_WITH_ASSISTANT_OBJECT = ALL_SETTINGS_OBJECT;
const SETTINGS_WITH_ASSISTANT_OBJECT = cloneDeep(ALL_SETTINGS_OBJECT);

SETTINGS_WITH_ASSISTANT_OBJECT.assistants.assistants = [
{
id: 'assistant1',
name: 'GraaspBot',
message: [
configuration: [
{
role: 'system',
content:
'You are a helpful assistant. You always give your most creative ideas.',
},
],
type: AssistantType.LLM,
},
];
SETTINGS_WITH_ASSISTANT_OBJECT.activity.steps = [
Expand Down Expand Up @@ -144,3 +145,48 @@ SETTINGS_WITH_ASSISTANT_OBJECT.activity.steps = [
export const SETTINGS_WITH_ASSISTANT = Object.entries(
SETTINGS_WITH_ASSISTANT_OBJECT,
).map(([key, value]) => newSettingFactory(key, value));

const SETTINGS_WITH_RATINGS_OBJECT = cloneDeep(ALL_SETTINGS_OBJECT);

SETTINGS_WITH_RATINGS_OBJECT.activity.steps = [
{
type: ActivityType.Collection,
round: 0,
time: 1,
},
{
type: ActivityType.Evaluation,
round: 1,
time: 1,
evaluationType: EvaluationType.Rate,
evaluationParameters: {
ratings: [
{
name: 'Usefulness',
description: 'How useful is the response',
maxLabel: 'Useful',
minLabel: 'Useless',
levels: 5,
},
{
name: 'Novelty',
description: 'How novel is the response',
maxLabel: 'Novel',
minLabel: 'Common',
levels: 5,
},
],
ratingsName: 'Usefulness and novelty',
},
},
{
type: ActivityType.Results,
round: 1,
time: 240,
resultsType: EvaluationType.Rate,
},
];

export const SETTINGS_WITH_RATINGS = Object.entries(
SETTINGS_WITH_RATINGS_OBJECT,
).map(([key, value]) => newSettingFactory(key, value));
1 change: 1 addition & 0 deletions src/langs/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
"VOTES_LEFT": "{{availableVotes}} votes left out of {{maxNumberOfVotes}}"
},
"NO_VISUALIZATION": "No visualization is available for this type of evaluation.",
"NO_DATA_FOR_RATING": "No data for {{ratingName}}",
"USEFULNESS_NOVELTY": {
"COMMON": "Common",
"NOVEL": "Novel",
Expand Down
53 changes: 31 additions & 22 deletions src/modules/common/response/visualization/RatingsVisualization.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

import CircularProgress from '@mui/material/CircularProgress';
import { LinearProgress, Typography } from '@mui/material';
import Stack from '@mui/material/Stack';

import { RatingData } from '@/config/appDataTypes';
import { TRANSLATIONS_NS } from '@/config/i18n';
import { useRatingsContext } from '@/modules/context/RatingsContext';

import CircularIndicator from './indicators/CircularIndicator';
Expand All @@ -15,6 +17,7 @@ interface RatingsVisualizationProps {
const RatingsVisualization: FC<RatingsVisualizationProps> = ({
responseId,
}): JSX.Element => {
const { t } = useTranslation(TRANSLATIONS_NS, { keyPrefix: 'EVALUATION' });
const {
ratings: ratingsDef,
getRatingsStatsForResponse,
Expand All @@ -29,11 +32,6 @@ const RatingsVisualization: FC<RatingsVisualizationProps> = ({
getRatingsStatsForResponse(responseId).then((d) => setRatings(d));
}, [getRatingsStatsForResponse, responseId]);

if (typeof ratingsDef === 'undefined' || typeof ratings === 'undefined') {
// TODO: Make that look good.
return <CircularProgress />;
}

const nbrRatings = ratingsDef?.length ?? 0;

return (
Expand All @@ -44,22 +42,33 @@ const RatingsVisualization: FC<RatingsVisualizationProps> = ({
justifyContent="center"
m={2}
>
{ratingsDef.map((singleRatingDefinition, index) => {
const { name } = singleRatingDefinition;
if (ratings) {
const result = ratings[index];
return (
<CircularIndicator
key={index}
value={result.value}
thresholds={ratingsThresholds}
label={name}
width={`${100 / nbrRatings}%`}
/>
);
}
return <CircularProgress key={index} />;
})}
{typeof ratingsDef === 'undefined' || typeof ratings === 'undefined' ? (
<LinearProgress />
) : (
ratingsDef.map((singleRatingDefinition, index) => {
const { name } = singleRatingDefinition;
if (ratings) {
const result = ratings[index];
if (typeof result === 'undefined') {
return (
<Typography key={index} variant="caption">
{t('NO_DATA_FOR_RATING', { ratingName: name })}
</Typography>
);
}
return (
<CircularIndicator
key={index}
value={result.value}
thresholds={ratingsThresholds}
label={name}
width={`${100 / nbrRatings}%`}
/>
);
}
return <LinearProgress key={index} />;
})
)}
</Stack>
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/modules/context/RatingsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ export const RatingsProvider: FC<RatingsContextProps> = ({
const extractedRatings = ratingsForResponse.map(
({ data }) => data.ratings,
);
if (extractedRatings.length === 0) {
return undefined;
}

const initialVal = extractedRatings[0].map((r) => ({
...r,
Expand Down

0 comments on commit 352736f

Please sign in to comment.