Skip to content

Commit

Permalink
Add Token Counter Displayer in Repl (#2775)
Browse files Browse the repository at this point in the history
* Create new InterpreterOutput type in repl for notifications

* Add notification card in Repl component

* Add style to notification ReplOutput

* Add fields to workspace state for storing notification string

* Create logic for handling custom notifications and token counter string

* Create button in ground control enable/disable token counter

* Edit upload assessment form data to include hasTokenCounter field

* Add workspace actions to handle enabling token counter

* Revert "Edit upload assessment form data to include hasTokenCounter field"

This reverts commit b087c55.

* Revert "Create button in ground control enable/disable token counter"

This reverts commit 8b1137a.

* Add hasTokenCount toggle button on assessment configuration panel

* Integrate token counter

* Conditionally render token counter depending on assessment config

* Remove console.log

* Fix bug where turning off token counter would not work in repl

* Fix compile time errors

* Make token count message more concise

* Refactor code for readability and css best practices
  • Loading branch information
jayjay19630 authored Feb 13, 2024
1 parent 1208290 commit 4f55b88
Show file tree
Hide file tree
Showing 17 changed files with 184 additions and 8 deletions.
20 changes: 19 additions & 1 deletion src/commons/application/ApplicationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,22 @@ export type ErrorOutput = {
consoleLogs: string[];
};

export type InterpreterOutput = RunningOutput | CodeOutput | ResultOutput | ErrorOutput;
/**
* An output which represents a message being displayed to the user. Not a true
* result from the program, but rather a customised notification meant to highlight
* events that occur outside execution of the program.
*/
export type NotificationOutput = {
type: 'notification';
consoleLog: string;
};

export type InterpreterOutput =
| RunningOutput
| CodeOutput
| ResultOutput
| ErrorOutput
| NotificationOutput;

export enum Role {
Student = 'student',
Expand Down Expand Up @@ -360,6 +375,9 @@ export const createDefaultWorkspace = (workspaceLocation: WorkspaceLocation): Wo
originalValue: ''
},
replValue: '',
hasTokenCounter: false,
tokenCount: 0,
customNotification: '',
sharedbConnected: false,
stepLimit: 1000,
globals: [],
Expand Down
9 changes: 9 additions & 0 deletions src/commons/application/actions/__tests__/SessionActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ test('setAssessmentConfigurations generates correct action object', () => {
type: 'Mission1',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -282,6 +283,7 @@ test('setAssessmentConfigurations generates correct action object', () => {
type: 'Mission2',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -290,6 +292,7 @@ test('setAssessmentConfigurations generates correct action object', () => {
type: 'Mission3',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
}
Expand Down Expand Up @@ -640,6 +643,7 @@ test('updateAssessmentTypes generates correct action object', () => {
type: 'Missions',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -648,6 +652,7 @@ test('updateAssessmentTypes generates correct action object', () => {
type: 'Quests',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -656,6 +661,7 @@ test('updateAssessmentTypes generates correct action object', () => {
type: 'Paths',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -664,6 +670,7 @@ test('updateAssessmentTypes generates correct action object', () => {
type: 'Contests',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand All @@ -672,6 +679,7 @@ test('updateAssessmentTypes generates correct action object', () => {
type: 'Others',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
}
Expand All @@ -689,6 +697,7 @@ test('deleteAssessmentConfig generates correct action object', () => {
type: 'Mission1',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
};
Expand Down
2 changes: 2 additions & 0 deletions src/commons/assessment/AssessmentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export type Assessment = {
type: AssessmentType;
globalDeployment?: Library; // For mission control
graderDeployment?: Library; // For mission control
hasTokenCounter?: boolean;
id: number;
longSummary: string;
missionPDF: string;
Expand All @@ -95,6 +96,7 @@ export type AssessmentConfiguration = {
displayInDashboard: boolean;
hoursBeforeEarlyXpDecay: number;
earlySubmissionXp: number;
hasTokenCounter: boolean;
};

export interface IProgrammingQuestion extends BaseQuestion {
Expand Down
1 change: 1 addition & 0 deletions src/commons/assessment/__tests__/Assessment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const mockAssessmentProps = assertType<AssessmentProps>()({
type: 'Missions',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
}
Expand Down
27 changes: 24 additions & 3 deletions src/commons/assessmentWorkspace/AssessmentWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ import {
changeExecTime,
changeSideContentHeight,
clearReplOutput,
disableTokenCounter,
enableTokenCounter,
evalEditor,
evalRepl,
evalTestcase,
Expand Down Expand Up @@ -111,6 +113,7 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
const { isMobileBreakpoint } = useResponsive();

const assessment = useTypedSelector(state => state.session.assessments.get(props.assessmentId));

const [selectedTab, setSelectedTab] = useState(
assessment?.questions[props.questionId].grader !== undefined
? SideContentType.grading
Expand Down Expand Up @@ -149,7 +152,9 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
handleEditorUpdateBreakpoints,
handleReplEval,
handleSave,
handleUpdateHasUnsavedChanges
handleUpdateHasUnsavedChanges,
handleEnableTokenCounter,
handleDisableTokenCounter
} = useMemo(() => {
return {
handleTestcaseEval: (id: number) => dispatch(evalTestcase(workspaceLocation, id)),
Expand All @@ -173,7 +178,9 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
handleSave: (id: number, answer: number | string | ContestEntry[]) =>
dispatch(submitAnswer(id, answer)),
handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) =>
dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges))
dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)),
handleEnableTokenCounter: () => dispatch(enableTokenCounter(workspaceLocation)),
handleDisableTokenCounter: () => dispatch(disableTokenCounter(workspaceLocation))
};
}, [dispatch]);

Expand Down Expand Up @@ -238,6 +245,21 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
checkWorkspaceReset();
});

/**
* Handles toggling enabling and disabling token counter depending on assessment properties
*/
useEffect(() => {
if (props.assessmentConfiguration.hasTokenCounter) {
handleEnableTokenCounter();
} else {
handleDisableTokenCounter();
}
}, [
props.assessmentConfiguration.hasTokenCounter,
handleEnableTokenCounter,
handleDisableTokenCounter
]);

/**
* Handles toggling of relevant SideContentTabs when mobile breakpoint it hit
*/
Expand Down Expand Up @@ -839,7 +861,6 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
sideBarProps: sideBarProps,
mobileSideContentProps: mobileSideContentProps(questionId)
};

return (
<div className={classNames('WorkspaceParent', Classes.DARK)}>
{overlay}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const defaultProps = assertType<AssessmentWorkspaceProps>()({
type: 'Missions',
isManuallyGraded: true,
displayInDashboard: true,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 48,
earlySubmissionXp: 200
},
Expand Down
8 changes: 8 additions & 0 deletions src/commons/mocks/AssessmentMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -30,6 +31,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -38,6 +40,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -46,6 +49,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: true,
earlySubmissionXp: 200
},
{
Expand All @@ -54,6 +58,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
}
],
Expand All @@ -64,6 +69,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -72,6 +78,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -80,6 +87,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
}
]
Expand Down
1 change: 1 addition & 0 deletions src/commons/profile/__tests__/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const assessmentConfigurations: AssessmentConfiguration[] = [
type: c,
isManuallyGraded: false,
displayInDashboard: false,
hasTokenCounter: false,
hoursBeforeEarlyXpDecay: 0,
earlySubmissionXp: 0
}));
Expand Down
6 changes: 6 additions & 0 deletions src/commons/repl/Repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export const Output: React.FC<OutputProps> = (props: OutputProps) => {
</Card>
);
}
case 'notification':
return (
<Card className="notification-output-container">
<Pre className="notification-output">{'💡 ' + props.output.consoleLog}</Pre>
</Card>
);
default:
return <Card>''</Card>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/commons/sagas/BackendSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,6 @@ function* BackendSaga(): SagaIterator {
getAssessmentConfigs,
tokens
);

if (assessmentConfigs) {
yield put(actions.setAssessmentConfigurations(assessmentConfigs));
}
Expand Down Expand Up @@ -982,6 +981,7 @@ function* BackendSaga(): SagaIterator {
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 0,
hasTokenCounter: false,
earlySubmissionXp: 0
}
];
Expand Down
10 changes: 9 additions & 1 deletion src/commons/sagas/WorkspaceSaga.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { tokenizer } from 'acorn';
import { FSModule } from 'browserfs/dist/node/core/FS';
import {
Context,
Expand All @@ -9,7 +10,7 @@ import {
runFilesInContext,
runInContext
} from 'js-slang';
import { TRY_AGAIN } from 'js-slang/dist/constants';
import { ACORN_PARSE_OPTIONS, TRY_AGAIN } from 'js-slang/dist/constants';
import { defineSymbol } from 'js-slang/dist/createContext';
import { InterruptedError } from 'js-slang/dist/errors/errors';
import { parse } from 'js-slang/dist/parser/parser';
Expand Down Expand Up @@ -1256,6 +1257,13 @@ export function* evalCode(

yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv);

// Change token count if its assessment and EVAL_EDITOR
if (actionType === EVAL_EDITOR && workspaceLocation === 'assessment') {
const tokens = [...tokenizer(entrypointCode, ACORN_PARSE_OPTIONS)];
const tokenCounter = tokens.length;
yield put(actions.setTokenCount(workspaceLocation, tokenCounter));
}

// Do not write interpreter output to REPL, if executing chunks (e.g. prepend/postpend blocks)
if (actionType !== EVAL_SILENT) {
if (!isStoriesBlock) {
Expand Down
6 changes: 6 additions & 0 deletions src/commons/sagas/__tests__/BackendSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -226,6 +227,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -234,6 +236,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [
isManuallyGraded: false,
displayInDashboard: false,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
},
{
Expand All @@ -242,6 +245,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [
isManuallyGraded: false,
displayInDashboard: false,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: true,
earlySubmissionXp: 200
},
{
Expand All @@ -250,6 +254,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [
isManuallyGraded: true,
displayInDashboard: false,
hoursBeforeEarlyXpDecay: 48,
hasTokenCounter: false,
earlySubmissionXp: 200
}
];
Expand Down Expand Up @@ -1002,6 +1007,7 @@ describe('Test CREATE_COURSE action', () => {
isManuallyGraded: true,
displayInDashboard: true,
hoursBeforeEarlyXpDecay: 0,
hasTokenCounter: false,
earlySubmissionXp: 0
}
];
Expand Down
Loading

0 comments on commit 4f55b88

Please sign in to comment.