Skip to content

Commit b5958ce

Browse files
2138 add summary (#3902)
* Get exercises working in docker - Add new compose file for exercises - Update dockerfile to use node 18 - Listen on 0.0.0.0 instead of localhost - Use node-sass 9 - Fix build and serve commands * Update bin scripts to use npx * Add "summary" solution type to question model * Add summary to question component * Only display "detailed" solutions in question preview component * Add tests and update snapshots * Check solution_type in unit test Also update the docker-quickstart
1 parent 5f30239 commit b5958ce

File tree

17 files changed

+696
-172
lines changed

17 files changed

+696
-172
lines changed

Dockerfile

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
FROM node:10.9-slim as builder
1+
FROM node:18-slim as base
22

33
RUN apt-get update && apt-get install -y \
44
build-essential \
55
g++ \
66
gcc \
77
git \
8-
python2.7 \
9-
&& rm -rf /var/lib/apt/lists/*
8+
curl \
9+
# Additional packages required by python build
10+
zlib1g \
11+
zlib1g-dev \
12+
libssl-dev \
13+
libbz2-dev \
14+
libsqlite3-dev \
15+
&& rm -rf /var/lib/apt/lists/*
16+
17+
FROM base as builder
18+
19+
ENV PATH=/root/.pyenv/bin:$PATH
20+
RUN curl https://pyenv.run | bash \
21+
&& eval "$(pyenv virtualenv-init -)" \
22+
&& pyenv install 2.7 \
23+
&& ln -s /root/.pyenv/shims/python /usr/bin/python
1024

11-
RUN ln -s /usr/bin/python2.7 /usr/bin/python2
1225

1326
FROM builder as build
1427
ARG PUBLIC_PATH

bin/build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if [ "$(version "$npm_version")" -lt "$(version "$required_version")" ]; then
1919
fi
2020

2121
[ -d $OX_PROJECT/dist ] && rm -r $OX_PROJECT/dist
22-
$(npm bin)/webpack --config webpack.config.js
22+
NODE_OPTIONS="--openssl-legacy-provider" npx webpack --config webpack.config.js
2323

2424
# we're done unless the "archive" flag from the ansible build is set
2525
if [ "$2" != "archive" ]; then

bin/lint

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ if [ $# -ne 0 ]; then
88
if [ "$1" == "staged" ]; then
99
files=`git status --short */{src,specs} | egrep '^[^D|^R].*([j|t]sx?)$' | awk '{ print $2 }'`
1010
if [ ! -z "$files" ]; then
11-
$(npm bin)/eslint $2 $files
11+
npx eslint $2 $files
1212
fi
1313
elif [ "$1" == "fix" ]; then
14-
$(npm bin)/eslint --ext $EXT {tutor,shared,exercises}/{src,specs} --fix
14+
npx eslint --ext $EXT {tutor,shared,exercises}/{src,specs} --fix
1515
else
16-
$(npm bin)/eslint --ext $EXT $1/{src,specs}
16+
npx eslint --ext $EXT $1/{src,specs}
1717
fi
1818
exit $?
1919
fi
2020

2121
if [[ ! -z "${OX_PROJECT}" ]]; then
22-
$(npm bin)/eslint --ext $EXT $OX_PROJECT/{src,specs}
22+
npx eslint --ext $EXT $OX_PROJECT/{src,specs}
2323
exit $?
2424
fi
2525

26-
$(npm bin)/eslint --ext $EXT {tutor,shared,exercises}/{src,specs}
26+
npx eslint --ext $EXT {tutor,shared,exercises}/{src,specs}

bin/serve

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ echo Serving: $OX_PROJECT
1010
if [ x$2 == x"standalone" ]; then
1111
node tutor/specs/acceptance/server/index.js
1212
else
13-
node --max_old_space_size=2048 $(npm bin)/webpack-dev-server --debug --progress --config webpack.config.js
13+
NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=2048" npx webpack-dev-server --debug --progress --config webpack.config.js
1414
fi

bin/test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ done
3737
echo Test: $OX_PROJECT
3838

3939
if [[ "$OX_PROJECT" = "e2e" ]]; then
40-
$(npm bin)/playwright test --config configs/playwright.config.ts "$OTHER_ARGS"
40+
npx playwright test --config configs/playwright.config.ts "$OTHER_ARGS"
4141
if [[ "$WATCH" = "t" ]]; then
42-
$(npm bin)/chokidar ./tutor/specs/e2e/*.ts -d 1500 -c '$(npm bin)/playwright test --config configs/playwright.config.ts {path}'
42+
npx chokidar ./tutor/specs/e2e/*.ts -d 1500 -c 'npx playwright test --config configs/playwright.config.ts {path}'
4343
fi
4444
else
4545
$DIR/test-unit $OX_PROJECT $OTHER_ARGS

bin/test-unit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ if [[ -z "$NODE_OPTIONS" ]]; then
1111
export NODE_OPTIONS="--max-old-space-size=768"
1212
fi
1313

14-
$(npm bin)/jest --verbose false --config ./configs/test/jest.$1.js $args
14+
npx jest --verbose false --config ./configs/test/jest.$1.js $args

docker-compose.exercises.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: '3.5'
2+
services:
3+
build:
4+
command: bash -c "if [ -z \"`ls -A node_modules`\" ]; then yarn; fi && yarn serve exercises"
5+
build:
6+
context: .
7+
target: builder
8+
working_dir: /code
9+
environment:
10+
- "OX_PROJECT_HOST=0.0.0.0"
11+
volumes:
12+
- .:/code:cached
13+
ports:
14+
- "8001:8001"

docker-compose.override.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
version: '3.5'
22
services:
33
build:
4-
command: bash -c "if [ -z \"`ls -A node_modules`\" ]; then yarn; fi && yarn serve tutor"
4+
command: bash -c "if [ -z \"`ls -A node_modules`\" ]; then yarn; fi && yarn run serve tutor"
55
build:
66
context: .
77
target: builder
88
working_dir: /code
9+
environment:
10+
- "OX_PROJECT_HOST=0.0.0.0"
911
volumes:
1012
- .:/code:cached
1113
ports:

docker-quickstart.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ the following projects must be separtely cloned and run for this project to work
1616

1717
[openstax/tutor-server](https://github.com/openstax/tutor-server)
1818

19-
## Run
19+
## Run Tutor
2020

2121
``` bash
2222
docker-compose up
@@ -26,6 +26,16 @@ the js files will be served at `http://localhost:8000`
2626

2727
the ui is available through the tutor-server project at `http://localhost:3001`
2828

29+
## Run Exercises frontend
30+
31+
``` bash
32+
docker-compose -f docker-compose.exercises.yml up
33+
```
34+
35+
the js files will be served at `http://localhost:8001`
36+
37+
You will need to start the exercises backend independently
38+
2939
## node_modules
3040

3141
if you need to update node modules run:
@@ -34,8 +44,14 @@ if you need to update node modules run:
3444
docker-compose exec build yarn
3545
```
3646

37-
in general you can run anythin in the build container with
47+
in general you can run anything in the build container with
3848

3949
```
4050
docker-compose exec build <command>
4151
```
52+
53+
## Troubleshooting
54+
55+
if you run into problems, try the following:
56+
* rebuilding the build image with `docker-compose build`
57+
* remove your node_modules directory

exercises/specs/components/__snapshots__/exercise.spec.js.snap

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,13 @@ exports[`Exercises component renders and matches snapshot 1`] = `
571571
onChange={[Function]}
572572
value="four"
573573
/>
574+
<label>
575+
Summary
576+
</label>
577+
<textarea
578+
onChange={[Function]}
579+
value=""
580+
/>
574581
</div>
575582
</div>
576583
</div>
@@ -2893,6 +2900,13 @@ exports[`Exercises component renders with intro and a multiple questions when ex
28932900
onChange={[Function]}
28942901
value="four"
28952902
/>
2903+
<label>
2904+
Summary
2905+
</label>
2906+
<textarea
2907+
onChange={[Function]}
2908+
value=""
2909+
/>
28962910
</div>
28972911
</div>
28982912
</div>
@@ -3689,6 +3703,13 @@ exports[`Exercises component renders with intro and a multiple questions when ex
36893703
onChange={[Function]}
36903704
value="four"
36913705
/>
3706+
<label>
3707+
Summary
3708+
</label>
3709+
<textarea
3710+
onChange={[Function]}
3711+
value=""
3712+
/>
36923713
</div>
36933714
</div>
36943715
</div>
@@ -4114,6 +4135,13 @@ exports[`Exercises component renders with intro and a multiple questions when ex
41144135
onChange={[Function]}
41154136
value="four"
41164137
/>
4138+
<label>
4139+
Summary
4140+
</label>
4141+
<textarea
4142+
onChange={[Function]}
4143+
value=""
4144+
/>
41174145
</div>
41184146
</div>
41194147
</div>
@@ -6665,6 +6693,10 @@ exports[`Exercises component resets fields when model is new 1`] = `
66656693
Detailed Solution
66666694
</label>
66676695
<textarea onChange={[Function: updateSolution] { isMobxAction: true }} value=\\"four\\" />
6696+
<label>
6697+
Summary
6698+
</label>
6699+
<textarea onChange={[Function: updateSummary] { isMobxAction: true }} value=\\"\\" />
66686700
</div>
66696701
</div>
66706702
</Question>

exercises/src/components/exercise/question.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class Question extends React.Component {
4242
question.collaborator_solution_html = event.target.value;
4343
}
4444

45+
@action.bound updateSummary(event) {
46+
const { question } = this.props;
47+
question.summary_html = event.target.value;
48+
}
49+
4550
@action.bound addAnswer() {
4651
this.props.question.answers.push(new AnswerModel());
4752
}
@@ -139,6 +144,10 @@ class Question extends React.Component {
139144
Detailed Solution
140145
</label>
141146
<textarea onChange={this.updateSolution} value={question.collaborator_solution_html} />
147+
<label>
148+
Summary
149+
</label>
150+
<textarea onChange={this.updateSummary} value={question.summary_html} />
142151
</div>
143152
</div>
144153
);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@
212212
"jsdom": "^16.5.0",
213213
"json-server": "^0.15.0",
214214
"lighthouse": "^5.2.0",
215-
"node-sass": "^7.0.0",
215+
"node-sass": "^9.0.0",
216216
"object-factory-bot": "0.8.2",
217217
"pixelmatch": "^5.0.2",
218218
"pngjs": "^3.4.0",

shared/specs/model/exercise/question.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,36 @@ describe('Exercise Question', () => {
8181
question.collaborator_solution_html = '';
8282
expect(question.collaborator_solutions).toHaveLength(0);
8383
});
84+
it('#summary_html', () => {
85+
question.collaborator_solutions.clear()
86+
expect(question.collaborator_solutions).toHaveLength(0);
87+
question.summary_html = 'one, two, three';
88+
expect(question.collaborator_solutions).toHaveLength(1);
89+
expect(question.collaborator_solutions[0].content_html).toEqual('one, two, three');
90+
question.summary_html = 'four';
91+
expect(question.collaborator_solutions[0].content_html).toEqual('four');
92+
question.summary_html = '';
93+
expect(question.collaborator_solutions).toHaveLength(0);
94+
});
95+
it('can handle both summary and detailed solution types', () => {
96+
question.collaborator_solutions.clear()
97+
expect(question.collaborator_solutions).toHaveLength(0);
98+
question.summary_html = 'one, two, three';
99+
question.collaborator_solution_html = 'four, five, six';
100+
expect(question.collaborator_solutions).toHaveLength(2);
101+
expect(question.collaborator_solutions[0].content_html).toEqual('one, two, three');
102+
expect(question.collaborator_solutions[1].content_html).toEqual('four, five, six');
103+
expect(question.collaborator_solutions[0].solution_type).toEqual('summary');
104+
expect(question.collaborator_solutions[1].solution_type).toEqual('detailed');
105+
question.summary_html = 'four';
106+
expect(question.collaborator_solutions[0].content_html).toEqual('four');
107+
question.summary_html = '';
108+
expect(question.collaborator_solutions).toHaveLength(1);
109+
expect(question.collaborator_solutions[0].content_html).toEqual('four, five, six');
110+
expect(question.collaborator_solutions[0].solution_type).toEqual('detailed');
111+
question.collaborator_solution_html = '';
112+
expect(question.collaborator_solutions).toHaveLength(0);
113+
});
84114
it('uses answers for detecting multi-choice', () => {
85115
expect(question.isMultipleChoice).toBe(true);
86116
question.answers.clear()

shared/src/components/question/index.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ class Question extends React.Component {
144144
{...htmlAndMathProps}
145145
className="solution"
146146
block={true}
147-
html={map(collaborator_solutions, 'content_html').join('')} />
147+
html={map(collaborator_solutions.filter(
148+
(sol) => sol.solution_type === 'detailed'
149+
), 'content_html').join('')} />
148150
</div>;
149151
}
150152

shared/src/model/exercise/question.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,34 @@ class ExerciseQuestion extends BaseModel {
5151
modelize(this)
5252
}
5353

54+
private findSolutionByType(solution_type: string) {
55+
return this.collaborator_solutions.findIndex(
56+
(sol) => sol.solution_type === solution_type
57+
);
58+
}
59+
60+
private setSolutionByType(solution_type: string, val: string) {
61+
let idx = this.findSolutionByType(solution_type);
62+
if (!val) {
63+
if (idx !== -1) {
64+
this.collaborator_solutions.splice(idx, 1);
65+
}
66+
} else {
67+
if (idx === -1) {
68+
const newSolution = new Solution;
69+
newSolution.solution_type = solution_type;
70+
idx = this.collaborator_solutions.push(newSolution) - 1;
71+
}
72+
this.collaborator_solutions[idx].content_html = val;
73+
}
74+
}
75+
76+
private getSolutionHTMLByType(solution_type: string) {
77+
const idx = this.findSolutionByType(solution_type)
78+
return idx !== -1 ?
79+
this.collaborator_solutions[idx].content_html : '';
80+
}
81+
5482
@computed get allowedFormatTypes() {
5583
const type = this.exercise.tags.withType('type');
5684
if (type && type.value != 'practice') {
@@ -90,21 +118,19 @@ class ExerciseQuestion extends BaseModel {
90118
}
91119

92120
@computed get collaborator_solution_html() {
93-
return this.collaborator_solutions.length ?
94-
this.collaborator_solutions[0].content_html : '';
121+
return this.getSolutionHTMLByType('detailed');
95122
}
96123

97124
set collaborator_solution_html(val) {
98-
if (!val) {
99-
if (this.collaborator_solutions.length) {
100-
this.collaborator_solutions.clear()
101-
}
102-
} else {
103-
if (!this.collaborator_solutions.length) {
104-
this.collaborator_solutions.push(new Solution);
105-
}
106-
this.collaborator_solutions[0].content_html = val;
107-
}
125+
this.setSolutionByType('detailed', val);
126+
}
127+
128+
@computed get summary_html() {
129+
return this.getSolutionHTMLByType('summary');
130+
}
131+
132+
set summary_html(val) {
133+
this.setSolutionByType('summary', val);
108134
}
109135

110136
@computed get formatId() {

webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ const config = {
112112
quiet: false,
113113
noInfo: false,
114114
clientLogLevel: 'warning',
115-
host: 'localhost',
115+
host,
116116
filename: '[name].js',
117117
hot: !isCI,
118118
liveReload: !isCI,

0 commit comments

Comments
 (0)