Skip to content

Commit

Permalink
Explicitly separate problem set problems in a chapter (#667)
Browse files Browse the repository at this point in the history
  • Loading branch information
fushar authored Oct 6, 2024
1 parent dd60873 commit c897a26
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
public interface ChapterProblemsResponse {
List<ChapterProblem> getData();
Map<String, ProblemInfo> getProblemsMap();
Map<String, List<List<String>>> getProblemSetProblemPathsMap();
Map<String, ProblemProgress> getProblemProgressesMap();

class Builder extends ImmutableChapterProblemsResponse.Builder {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package judgels.jerahmeel.api.chapter.problem.programming;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import judgels.gabriel.api.SubmissionSource;
Expand All @@ -18,6 +19,7 @@ public interface ChapterProblemWorksheet extends judgels.jerahmeel.api.chapter.p
Set<ProblemSkeleton> getSkeletons();
Optional<Submission> getLastSubmission();
Optional<SubmissionSource> getLastSubmissionSource();
List<List<String>> getProblemSetProblemPaths();
ProblemProgress getProgress();
Optional<ProblemEditorialInfo> getEditorial();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import judgels.jerahmeel.api.problem.ProblemProgress;
import judgels.jerahmeel.chapter.ChapterStore;
import judgels.jerahmeel.chapter.resource.ChapterResourceStore;
import judgels.jerahmeel.problemset.problem.ProblemSetProblemStore;
import judgels.jerahmeel.role.RoleChecker;
import judgels.jerahmeel.stats.StatsStore;
import judgels.jerahmeel.submission.JerahmeelSubmissionStore;
Expand All @@ -53,7 +54,8 @@ public class ChapterProblemResource {
@Inject protected RoleChecker roleChecker;
@Inject protected ChapterStore chapterStore;
@Inject protected ChapterResourceStore resourceStore;
@Inject protected ChapterProblemStore problemStore;
@Inject protected ChapterProblemStore chapterProblemStore;
@Inject protected ProblemSetProblemStore problemSetProblemStore;
@Inject protected StatsStore statsStore;
@Inject @JerahmeelSubmissionStore protected SubmissionStore submissionStore;
@Inject protected SubmissionSourceBuilder submissionSourceBuilder;
Expand Down Expand Up @@ -90,7 +92,7 @@ public void setProblems(
.build())
.collect(Collectors.toList());

problemStore.setProblems(chapterJid, setData);
chapterProblemStore.setProblems(chapterJid, setData);
}

@GET
Expand All @@ -103,15 +105,17 @@ public ChapterProblemsResponse getProblems(
String actorJid = actorChecker.check(authHeader);
checkFound(chapterStore.getChapterByJid(chapterJid));

List<ChapterProblem> problems = problemStore.getProblems(chapterJid);
List<ChapterProblem> problems = chapterProblemStore.getProblems(chapterJid);

var problemJids = Lists.transform(problems, ChapterProblem::getProblemJid);
Map<String, ProblemInfo> problemsMap = sandalphonClient.getProblems(problemJids);
Map<String, List<List<String>>> problemSetProblemPathsMap = problemSetProblemStore.getProblemSetProblemPathsMap(problemJids);
Map<String, ProblemProgress> problemProgressesMap = statsStore.getProblemProgressesMap(actorJid, problemJids);

return new ChapterProblemsResponse.Builder()
.data(problems)
.problemsMap(problemsMap)
.problemSetProblemPathsMap(problemSetProblemPathsMap)
.problemProgressesMap(problemProgressesMap)
.build();
}
Expand All @@ -131,7 +135,7 @@ public ChapterProblemWorksheet getProblemWorksheet(
String actorJid = actorChecker.check(authHeader);
checkFound(chapterStore.getChapterByJid(chapterJid));

ChapterProblem problem = checkFound(problemStore.getProblemByAlias(chapterJid, problemAlias));
ChapterProblem problem = checkFound(chapterProblemStore.getProblemByAlias(chapterJid, problemAlias));
String problemJid = problem.getProblemJid();
ProblemInfo problemInfo = sandalphonClient.getProblem(problemJid);

Expand All @@ -141,6 +145,7 @@ public ChapterProblemWorksheet getProblemWorksheet(

List<Optional<String>> previousAndNextResourcePaths =
resourceStore.getPreviousAndNextResourcePathsForProblem(chapterJid, problemAlias);
List<List<String>> problemSetProblemPaths = problemSetProblemStore.getProblemSetProblemPaths(problemJid);
ProblemProgress progress = statsStore.getProblemProgressesMap(actorJid, Set.of(problemJid)).get(problemJid);
Optional<ProblemEditorialInfo> editorial = progress.getVerdict().equals(Verdict.ACCEPTED.getCode())
? sandalphonClient.getProblemEditorial(problemJid, uriInfo.getBaseUri(), language)
Expand Down Expand Up @@ -169,6 +174,7 @@ public ChapterProblemWorksheet getProblemWorksheet(
.skeletons(sandalphonClient.getProgrammingProblemSkeletons(problemJid))
.lastSubmission(lastSubmission)
.lastSubmissionSource(lastSubmissionSource)
.problemSetProblemPaths(problemSetProblemPaths)
.progress(progress)
.editorial(editorial)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -15,18 +16,26 @@
import judgels.jerahmeel.api.problemset.problem.ProblemSetProblem;
import judgels.jerahmeel.persistence.ProblemContestDao;
import judgels.jerahmeel.persistence.ProblemContestModel;
import judgels.jerahmeel.persistence.ProblemSetDao;
import judgels.jerahmeel.persistence.ProblemSetModel;
import judgels.jerahmeel.persistence.ProblemSetProblemDao;
import judgels.jerahmeel.persistence.ProblemSetProblemModel;
import judgels.jerahmeel.persistence.ProblemSetProblemModel_;
import judgels.persistence.api.OrderDir;
import judgels.sandalphon.api.problem.ProblemType;

public class ProblemSetProblemStore {
private final ProblemSetDao problemSetDao;
private final ProblemSetProblemDao problemDao;
private final ProblemContestDao problemContestDao;

@Inject
public ProblemSetProblemStore(ProblemSetProblemDao problemDao, ProblemContestDao problemContestDao) {
public ProblemSetProblemStore(
ProblemSetDao problemSetDao,
ProblemSetProblemDao problemDao,
ProblemContestDao problemContestDao) {

this.problemSetDao = problemSetDao;
this.problemDao = problemDao;
this.problemContestDao = problemContestDao;
}
Expand Down Expand Up @@ -128,6 +137,29 @@ public ProblemSetProblem upsertProblem(
}
}

public Map<String, List<List<String>>> getProblemSetProblemPathsMap(Collection<String> problemJids) {
List<ProblemSetProblemModel> models = problemDao.selectAllByProblemJids(problemJids);

List<String> problemSetJids = Lists.transform(models, m -> m.problemSetJid);
Map<String, ProblemSetModel> problemSetModelsMap = problemSetDao.selectByJids(problemSetJids);

Map<String, List<List<String>>> problemSetProblemPathsMap = new HashMap<>();
for (ProblemSetProblemModel m : models) {
if (!problemSetModelsMap.containsKey(m.problemSetJid)) {
continue;
}
ProblemSetModel problemSetModel = problemSetModelsMap.get(m.problemSetJid);
List<String> path = List.of(problemSetModel.slug, m.alias);
problemSetProblemPathsMap.putIfAbsent(m.problemJid, new ArrayList<>());
problemSetProblemPathsMap.get(m.problemJid).add(path);
}
return Map.copyOf(problemSetProblemPathsMap);
}

public List<List<String>> getProblemSetProblemPaths(String problemJid) {
return getProblemSetProblemPathsMap(List.of(problemJid)).getOrDefault(problemJid, List.of());
}

private void upsertProblemContests(String problemJid, List<String> contestJids) {
problemContestDao.selectAllByProblemJid(problemJid).forEach(problemContestDao::delete);
problemContestDao.flush();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ function ChapterProblemStatementPage({ worksheet }) {
);
};

const renderProblemSetProblemPaths = () => {
const { problemSetProblemPaths } = worksheet;
if (!problemSetProblemPaths) {
return null;
}
return (
<small className="statement-header__problem-set-problem-paths">
{problemSetProblemPaths.map(p => p.join('/')).join(', ')}
</small>
);
};

const renderStatementHeader = () => {
const { defaultLanguage, languages } = worksheet;
if (!defaultLanguage || !languages) {
Expand All @@ -54,6 +66,7 @@ function ChapterProblemStatementPage({ worksheet }) {
<div className="statement-header">
<StatementLanguageWidget {...props} />
{renderLimits()}
{renderProblemSetProblemPaths()}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@
margin-left: auto;
margin-right: auto;

.statement-header__limits {
&__limits {
line-height: 26px !important;
margin-bottom: 5px;
white-space: nowrap;
}

&__problem-set-problem-paths {
flex: 1;
line-height: 26px !important;
}
}

.chapter-problem-editorial {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,26 @@ import { ProblemType } from '../../../../../../../../modules/api/sandalphon/prob

import './ChapterProblemCard.scss';

export function ChapterProblemCard({ course, chapter, problem, progress, problemName, isFuture }) {
export function ChapterProblemCard({
course,
chapter,
problem,
problemName,
problemSetProblemPaths,
progress,
isFuture,
}) {
const renderProblemSetProblemPaths = () => {
if (!problemSetProblemPaths) {
return null;
}
return (
<div className="chapter-problem-card__problem-set-problem-paths">
{problemSetProblemPaths.map(p => p.join('/')).join(', ')}
</div>
);
};

const renderProgress = () => {
if (!progress) {
return null;
Expand All @@ -29,6 +48,7 @@ export function ChapterProblemCard({ course, chapter, problem, progress, problem
<h4 data-key="name">
{problem.alias}. {problemName}
</h4>
{renderProblemSetProblemPaths()}
{renderProgress()}
</div>
</ContentCardLink>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
height: 21px;
}

&__problem-set-problem-paths {
font-weight: 600;
}

&--future {
background-color: inherit;

h4,
> *,
.chapter-problem-card__heading > .bp5-icon {
font-weight: normal !important;
color: #bcc0c2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@ export class ChapterResourcesPage extends Component {
this.setState({
response: undefined,
});

const response = await this.props.onGetResources(this.props.chapter.jid);
const [lessonsResponse, problemsResponse] = response;
const { data: lessons, lessonsMap } = lessonsResponse;
const { data: problems, problemsMap, problemSetProblemPathsMap, problemProgressesMap } = problemsResponse;

const firstUnsolvedProblemIndex = this.getFirstUnsolvedProblemIndex(problems, problemProgressesMap);

this.setState({
response,
lessons,
lessonsMap,
problems,
problemsMap,
problemSetProblemPathsMap,
problemProgressesMap,
firstUnsolvedProblemIndex,
});
};

Expand Down Expand Up @@ -68,15 +82,11 @@ export class ChapterResourcesPage extends Component {
};

renderResources = () => {
const { response } = this.state;
const { response, lessons, problems, problemSetProblemPathsMap } = this.state;
if (!response) {
return <LoadingContentCard />;
}

const [lessonsResponse, problemsResponse] = response;
const { data: lessons, lessonsMap } = lessonsResponse;
const { data: problems, problemsMap, problemProgressesMap } = problemsResponse;

if (lessons.length === 0 && problems.length === 0) {
return (
<p>
Expand All @@ -85,34 +95,66 @@ export class ChapterResourcesPage extends Component {
);
}

const firstUnsolvedProblemIndex = this.getFirstUnsolvedProblemIndex(problems, problemProgressesMap);
const chapterProblems = problems.filter(p => !problemSetProblemPathsMap[p.problemJid]);
const problemSetProblems = problems.filter(p => !!problemSetProblemPathsMap[p.problemJid]);

let chapterResources = null;
if (lessons.length > 0 || chapterProblems.length > 0) {
chapterResources = (
<div className="chapter-resources-page__resources">
{lessons.map(this.renderLesson)}
{chapterProblems.map(this.renderProblem)}
</div>
);
}

let problemSetResources = null;
if (problemSetProblems.length > 0) {
problemSetResources = (
<div className="chapter-resources-page__problem-set-problems">
<h4>Practice Problems</h4>
<div className="chapter-resources-page__resources">
{problemSetProblems.map((p, idx) => this.renderProblem(p, idx + chapterProblems.length))}
</div>
</div>
);
}

return (
<>
{lessons.map(lesson => {
const props = {
course: this.props.course,
chapter: this.props.chapter,
lesson,
lessonName: getLessonName(lessonsMap[lesson.lessonJid], undefined),
};
return <ChapterLessonCard key={lesson.lessonJid} {...props} />;
})}
{problems.map((problem, idx) => {
const props = {
course: this.props.course,
chapter: this.props.chapter,
problem,
problemName: getProblemName(problemsMap[problem.problemJid], undefined),
progress: problemProgressesMap[problem.problemJid],
isFuture: idx > firstUnsolvedProblemIndex,
};
return <ChapterProblemCard key={problem.problemJid} {...props} />;
})}
</>
<div className="chapter-resources-page__sections">
{chapterResources}
{problemSetResources}
</div>
);
};

renderLesson = lesson => {
const { lessonsMap } = this.state;

const props = {
course: this.props.course,
chapter: this.props.chapter,
lesson,
lessonName: getLessonName(lessonsMap[lesson.lessonJid], undefined),
};
return <ChapterLessonCard key={lesson.lessonJid} {...props} />;
};

renderProblem = (problem, idx) => {
const { problemsMap, problemSetProblemPathsMap, problemProgressesMap, firstUnsolvedProblemIndex } = this.state;

const props = {
course: this.props.course,
chapter: this.props.chapter,
problem,
problemName: getProblemName(problemsMap[problem.problemJid], undefined),
problemSetProblemPaths: problemSetProblemPathsMap[problem.problemJid],
progress: problemProgressesMap[problem.problemJid],
isFuture: idx > firstUnsolvedProblemIndex,
};
return <ChapterProblemCard key={problem.problemJid} {...props} />;
};

getFirstUnsolvedProblemIndex = (problems, problemProgressesMap) => {
for (let i = problems.length - 1; i >= 0; i--) {
const progress = problemProgressesMap[problems[i].problemJid];
Expand Down
Loading

0 comments on commit c897a26

Please sign in to comment.