Skip to content

Commit f4ad0a5

Browse files
committed
Examination Conflict-Based Statistics: GWT
- Examination Conflict-Based Statistics page rewritten to GWT (set unitime.legacy.examCBS to true for the old Struts-based page)
1 parent a01b2b7 commit f4ad0a5

13 files changed

Lines changed: 745 additions & 97 deletions

JavaSource/org/unitime/timetable/action/ExamCbsAction.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
*/
2020
package org.unitime.timetable.action;
2121

22+
import java.net.URLEncoder;
23+
import java.util.Enumeration;
24+
2225
import org.apache.struts2.convention.annotation.Action;
2326
import org.apache.struts2.convention.annotation.Result;
2427
import org.apache.struts2.tiles.annotation.TilesDefinition;
2528
import org.apache.struts2.tiles.annotation.TilesPutAttribute;
2629
import org.unitime.localization.impl.Localization;
2730
import org.unitime.localization.messages.ExaminationMessages;
31+
import org.unitime.timetable.defaults.ApplicationProperty;
2832
import org.unitime.timetable.form.ExamCbsForm;
2933
import org.unitime.timetable.security.rights.Right;
3034
import org.unitime.timetable.solver.exam.ui.ExamConflictStatisticsInfo;
@@ -46,6 +50,17 @@ public class ExamCbsAction extends UniTimeAction<ExamCbsForm> {
4650
protected static final ExaminationMessages MSG = Localization.create(ExaminationMessages.class);
4751

4852
public String execute() throws Exception {
53+
if (ApplicationProperty.LegacyExamCBS.isFalse()) {
54+
String url = "ecbs";
55+
boolean first = true;
56+
for (Enumeration<String> e = getRequest().getParameterNames(); e.hasMoreElements(); ) {
57+
String param = e.nextElement();
58+
url += (first ? "?" : "&") + param + "=" + URLEncoder.encode(getRequest().getParameter(param), "utf-8");
59+
first = false;
60+
}
61+
response.sendRedirect(url);
62+
return null;
63+
}
4964
// Check Access
5065
sessionContext.checkPermission(Right.ExaminationConflictStatistics);
5166

JavaSource/org/unitime/timetable/action/ExamInfoAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public String execute() throws Exception {
9999
form.setModel(model);
100100
model.apply(request, form);
101101

102-
if (op==null) {
102+
if (op==null || "Try".equals(op)) {
103103
model.clear(sessionContext.getUser());
104104
} else if (MSG.buttonApply().equals(op)) {
105105
model.refreshRooms();
@@ -135,7 +135,7 @@ public String execute() throws Exception {
135135
RoomAvailability.setAvailabilityWarning(request, session, model.getExam().getExamType().getUniqueId(), true, true);
136136
}
137137

138-
if ("Select".equals(op)) {
138+
if ("Select".equals(op) || "Try".equals(op)) {
139139
synchronized (model) {
140140
if (period != null)
141141
model.setPeriod(period);

JavaSource/org/unitime/timetable/defaults/ApplicationProperty.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3803,6 +3803,11 @@ public enum ApplicationProperty {
38033803
@Description("Examination Reports: switch the user interface back to the old (Struts-based) examination report page (Examinations > Examination Timetabling > Reports)")
38043804
LegacyExamAssignmentReport("unitime.legacy.examAssignmentReport"),
38053805

3806+
@Type(Boolean.class)
3807+
@DefaultValue("false")
3808+
@Description("Examination Conflict-Based Statistics: switch the user interface back to the old (Struts-based) examination conflict-based statistics page")
3809+
LegacyExamCBS("unitime.legacy.examCBS"),
3810+
38063811
@Type(Boolean.class)
38073812
@DefaultValue("false")
38083813
@Description("Student Scheduling: provide associated (parent) course -- a student requesting both courses cannot get the course without also getting the associated course")

JavaSource/org/unitime/timetable/gwt/client/Pages.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.unitime.timetable.gwt.client.events.EventRoomAvailability;
3131
import org.unitime.timetable.gwt.client.exams.AssignedExamsPage;
3232
import org.unitime.timetable.gwt.client.exams.ExamAssignmentChangesPage;
33+
import org.unitime.timetable.gwt.client.exams.ExamConflictBasedStatisticsPage;
3334
import org.unitime.timetable.gwt.client.exams.ExamDetailPage;
3435
import org.unitime.timetable.gwt.client.exams.ExamDistributionsEditPage;
3536
import org.unitime.timetable.gwt.client.exams.ExamDistributionsPage;
@@ -440,18 +441,22 @@ public enum Pages {
440441
assignedExams(new PageFactory() {
441442
public Widget create() { return new AssignedExamsPage(); }
442443
public String name(GwtMessages messages) { return messages.pageAssignedExaminations(); }
443-
}),
444+
}, SolverWarnings.Type.exam),
444445
notAssignedExams(new PageFactory() {
445446
public Widget create() { return new NotAssignedExamsPage(); }
446447
public String name(GwtMessages messages) { return messages.pageNotAssignedExaminations(); }
447-
}),
448+
}, SolverWarnings.Type.exam),
448449
examChanges(new PageFactory() {
449450
public Widget create() { return new ExamAssignmentChangesPage(); }
450451
public String name(GwtMessages messages) { return messages.pageExaminationAssignmentChanges(); }
451-
}),
452+
}, SolverWarnings.Type.exam),
452453
examAssignmentReport(new PageFactory() {
453454
public Widget create() { return new ExamReportsPage(); }
454455
public String name(GwtMessages messages) { return messages.pageExaminationReports(); }
456+
}, SolverWarnings.Type.exam),
457+
ecbs(new PageFactory() {
458+
public Widget create() { return new ExamConflictBasedStatisticsPage(); }
459+
public String name(GwtMessages messages) { return messages.pageExaminationConflictBasedStatistics(); }
455460
}),
456461
;
457462

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Licensed to The Apereo Foundation under one or more contributor license
3+
* agreements. See the NOTICE file distributed with this work for
4+
* additional information regarding copyright ownership.
5+
*
6+
* The Apereo Foundation licenses this file to you under the Apache License,
7+
* Version 2.0 (the "License"); you may not use this file except in
8+
* compliance with the License. You may obtain a copy of the License at:
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
*
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
package org.unitime.timetable.gwt.client.exams;
21+
22+
import org.unitime.localization.messages.ExaminationMessages;
23+
import org.unitime.timetable.gwt.client.ToolBox;
24+
import org.unitime.timetable.gwt.client.exams.ExamsInterface.ExamConflictBasedStatisticsRequest;
25+
import org.unitime.timetable.gwt.client.exams.ExamsInterface.ExamConflictStatisticsFilterRequest;
26+
import org.unitime.timetable.gwt.client.page.UniTimeNotifications;
27+
import org.unitime.timetable.gwt.client.solver.PageFilter;
28+
import org.unitime.timetable.gwt.client.solver.PreferenceLegend;
29+
import org.unitime.timetable.gwt.client.solver.suggestions.ConflictBasedStatisticsTree;
30+
import org.unitime.timetable.gwt.client.widgets.LoadingWidget;
31+
import org.unitime.timetable.gwt.client.widgets.P;
32+
import org.unitime.timetable.gwt.client.widgets.SimpleForm;
33+
import org.unitime.timetable.gwt.client.widgets.UniTimeFrameDialog;
34+
import org.unitime.timetable.gwt.command.client.GwtRpcResponseList;
35+
import org.unitime.timetable.gwt.command.client.GwtRpcService;
36+
import org.unitime.timetable.gwt.command.client.GwtRpcServiceAsync;
37+
import org.unitime.timetable.gwt.resources.GwtMessages;
38+
import org.unitime.timetable.gwt.shared.FilterInterface;
39+
import org.unitime.timetable.gwt.shared.CourseTimetablingSolverInterface.ConflictStatisticsFilterResponse;
40+
import org.unitime.timetable.gwt.shared.SolverInterface.PageMessage;
41+
import org.unitime.timetable.gwt.shared.SolverInterface.PageMessageType;
42+
import org.unitime.timetable.gwt.shared.SuggestionsInterface.CBSNode;
43+
import org.unitime.timetable.gwt.shared.SuggestionsInterface.SelectedAssignment;
44+
45+
import com.google.gwt.core.client.GWT;
46+
import com.google.gwt.event.dom.client.ClickEvent;
47+
import com.google.gwt.event.dom.client.ClickHandler;
48+
import com.google.gwt.event.logical.shared.ValueChangeEvent;
49+
import com.google.gwt.event.logical.shared.ValueChangeHandler;
50+
import com.google.gwt.user.client.History;
51+
import com.google.gwt.user.client.rpc.AsyncCallback;
52+
import com.google.gwt.user.client.ui.Composite;
53+
import com.google.gwt.user.client.ui.RootPanel;
54+
import com.google.gwt.user.client.ui.SimplePanel;
55+
56+
/**
57+
* @author Tomas Muller
58+
*/
59+
public class ExamConflictBasedStatisticsPage extends Composite {
60+
private static final GwtMessages MESSAGES = GWT.create(GwtMessages.class);
61+
private static final ExaminationMessages XMSG = GWT.create(ExaminationMessages.class);
62+
protected static GwtRpcServiceAsync RPC = GWT.create(GwtRpcService.class);
63+
private PageFilter iFilter;
64+
private SimplePanel iRootPanel;
65+
private SimpleForm iPanel;
66+
private FilterInterface iLastFilter;
67+
private GwtRpcResponseList<CBSNode> iLastResponse;
68+
private ConflictStatisticsFilterResponse iFilterResponse;
69+
private ConflictBasedStatisticsTree iTree;
70+
private PreferenceLegend iLegend;
71+
72+
public ExamConflictBasedStatisticsPage() {
73+
iFilter = new PageFilter();
74+
iFilter.getHeader().setCollapsible("1".equals(ToolBox.getSessionCookie("Exams.Filter")));
75+
iFilter.getHeader().addCollapsibleHandler(new ValueChangeHandler<Boolean>() {
76+
@Override
77+
public void onValueChange(ValueChangeEvent<Boolean> event) {
78+
ToolBox.setSessionCookie("Exams.Filter", event.getValue() ? "1" : "0");
79+
}
80+
});
81+
82+
iPanel = new SimpleForm(2);
83+
iPanel.removeStyleName("unitime-NotPrintableBottomLine");
84+
iPanel.addRow(iFilter);
85+
86+
iFilter.getFooter().addButton("search", MESSAGES.buttonSearch(), new ClickHandler() {
87+
@Override
88+
public void onClick(ClickEvent event) {
89+
String token = iFilter.getQuery();
90+
if (!History.getToken().equals(token))
91+
History.newItem(token, false);
92+
search(null);
93+
}
94+
});
95+
iFilter.getFooter().setEnabled("search", false);
96+
iRootPanel = new SimplePanel(iPanel);
97+
iRootPanel.addStyleName("unitime-ConflictBasedStatisticsPage");
98+
initWidget(iRootPanel);
99+
init();
100+
101+
History.addValueChangeHandler(new ValueChangeHandler<String>() {
102+
@Override
103+
public void onValueChange(ValueChangeEvent<String> event) {
104+
iFilter.setQuery(event.getValue(), true);
105+
if (iPanel.getRowCount() > 1)
106+
search(null);
107+
}
108+
});
109+
}
110+
111+
protected void init() {
112+
RPC.execute(new ExamConflictStatisticsFilterRequest(), new AsyncCallback<ConflictStatisticsFilterResponse>() {
113+
@Override
114+
public void onFailure(Throwable caught) {
115+
iFilter.getFooter().setErrorMessage(MESSAGES.failedToInitialize(caught.getMessage()));
116+
UniTimeNotifications.error(MESSAGES.failedToInitialize(caught.getMessage()), caught);
117+
ToolBox.checkAccess(caught);
118+
}
119+
120+
@Override
121+
public void onSuccess(ConflictStatisticsFilterResponse result) {
122+
iLegend = new PreferenceLegend(result.getSuggestionProperties().getPreferences());
123+
iFilterResponse = result;
124+
iFilter.getFooter().clearMessage();
125+
iFilter.setValue(result);
126+
iFilter.getFooter().setEnabled("search", true);
127+
createTriggers();
128+
if (iFilter.getHeader().isCollapsible() != null && !iFilter.getHeader().isCollapsible())
129+
search(null);
130+
}
131+
});
132+
}
133+
134+
public static native void createTriggers()/*-{
135+
$wnd.refreshPage = function() {
136+
};
137+
}-*/;
138+
139+
protected void search(final AsyncCallback<Boolean> callback) {
140+
final ExamConflictBasedStatisticsRequest request = new ExamConflictBasedStatisticsRequest();
141+
final FilterInterface filter = iFilter.getValue();
142+
try {
143+
request.setLimit(Double.valueOf(filter.getParameterValue("limit", "25.0")));
144+
} catch (NumberFormatException e) {}
145+
request.setVariableOriented("0".equals(filter.getParameterValue("mode", "0")));
146+
iFilter.getFooter().clearMessage();
147+
for (int row = iPanel.getRowCount() - 1; row > 0; row--)
148+
iPanel.removeRow(row);
149+
iFilter.getFooter().showLoading();
150+
iFilter.getFooter().setEnabled("search", false);
151+
LoadingWidget.showLoading(MESSAGES.waitLoadingData());
152+
RPC.execute(request, new AsyncCallback<GwtRpcResponseList<CBSNode>>() {
153+
@Override
154+
public void onFailure(Throwable caught) {
155+
LoadingWidget.hideLoading();
156+
iFilter.getFooter().setErrorMessage(MESSAGES.failedToLoadConflictStatistics(caught.getMessage()));
157+
UniTimeNotifications.error(MESSAGES.failedToLoadConflictStatistics(caught.getMessage()), caught);
158+
iFilter.getFooter().setEnabled("search", true);
159+
if (callback != null)
160+
callback.onFailure(caught);
161+
}
162+
163+
@Override
164+
public void onSuccess(GwtRpcResponseList<CBSNode> result) {
165+
LoadingWidget.hideLoading();
166+
iFilter.getFooter().clearMessage();
167+
populate(filter, result);
168+
iFilter.getFooter().setEnabled("search", true);
169+
if (callback != null)
170+
callback.onSuccess(result != null && !result.isEmpty());
171+
}
172+
});
173+
}
174+
175+
protected void populate(FilterInterface filter, GwtRpcResponseList<CBSNode> response) {
176+
iLastFilter = filter;
177+
iLastResponse = response;
178+
for (int row = iPanel.getRowCount() - 1; row > 0; row--)
179+
iPanel.removeRow(row);
180+
181+
RootPanel cpm = RootPanel.get("UniTimeGWT:CustomPageMessages");
182+
if (cpm != null && iFilterResponse != null) {
183+
cpm.clear();
184+
if (iFilterResponse.hasPageMessages()) {
185+
for (final PageMessage pm: iFilterResponse.getPageMessages()) {
186+
P p = new P(pm.getType() == PageMessageType.ERROR ? "unitime-PageError" : pm.getType() == PageMessageType.WARNING ? "unitime-PageWarn" : "unitime-PageMessage");
187+
p.setHTML(pm.getMessage());
188+
if (pm.hasUrl()) {
189+
p.addStyleName("unitime-ClickablePageMessage");
190+
p.addClickHandler(new ClickHandler() {
191+
@Override
192+
public void onClick(ClickEvent event) {
193+
if (pm.hasUrl()) ToolBox.open(GWT.getHostPageBaseURL() + pm.getUrl());
194+
}
195+
});
196+
}
197+
cpm.add(p);
198+
}
199+
}
200+
}
201+
202+
if (response == null || response.isEmpty()) {
203+
iFilter.getFooter().setMessage(MESSAGES.errorConflictStatisticsNoDataReturned());
204+
return;
205+
}
206+
207+
if (iTree == null) {
208+
iTree = new ConflictBasedStatisticsTree(iFilterResponse.getSuggestionProperties()) {
209+
protected void onClick(ClickEvent event, CBSNode node) {
210+
if (node.hasLink()) {
211+
ToolBox.open(GWT.getHostPageBaseURL() + node.getLink());
212+
} else if (node.hasClassId()) {
213+
UniTimeFrameDialog.openDialog(XMSG.dialogExamAssign(), "examInfo.action?menu=hide&examId=" + node.getClassId(), "900", "85%");
214+
} else if (node.hasSelection()) {
215+
SelectedAssignment selection = node.getSelection();
216+
UniTimeFrameDialog.openDialog(XMSG.dialogExamAssign(), "examInfo.action?menu=hide&examId=" + selection.getClassId()
217+
+ "&period=" + selection.getPatternId() + "&room=" + selection.getRoomIds(":") + "&op=Try&reset=1", "900", "85%");
218+
}
219+
}
220+
};
221+
}
222+
iTree.setValue(response);
223+
iPanel.addRow(iTree);
224+
iPanel.addRow(iLegend);
225+
}
226+
}

JavaSource/org/unitime/timetable/gwt/client/exams/ExamsInterface.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.unitime.timetable.gwt.command.client.GwtRpcRequest;
3939
import org.unitime.timetable.gwt.command.client.GwtRpcResponse;
4040
import org.unitime.timetable.gwt.command.client.GwtRpcResponseList;
41+
import org.unitime.timetable.gwt.shared.CourseTimetablingSolverInterface.ConflictStatisticsFilterRequest;
42+
import org.unitime.timetable.gwt.shared.SuggestionsInterface.ConflictBasedStatisticsRequest;
4143

4244
import com.google.gwt.user.client.rpc.IsSerializable;
4345

@@ -550,4 +552,12 @@ public static class ExamAssignmentChangesRequest extends AssignedExamsRequest {}
550552
public static class ExamReportsFilterRequest extends AssignedExamsFilterRequest {}
551553

552554
public static class ExamReportsRequest extends AssignedExamsRequest {}
555+
556+
public static class ExamConflictStatisticsFilterRequest extends ConflictStatisticsFilterRequest {
557+
private static final long serialVersionUID = 1L;
558+
}
559+
560+
public static class ExamConflictBasedStatisticsRequest extends ConflictBasedStatisticsRequest {
561+
private static final long serialVersionUID = 1L;
562+
}
553563
}

JavaSource/org/unitime/timetable/gwt/resources/GwtMessages.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4067,6 +4067,14 @@ public interface GwtMessages extends Messages {
40674067
@DefaultMessage("Examination Assignment Changes")
40684068
@DoNotTranslate
40694069
String pageExaminationAssignmentChanges();
4070+
4071+
@DefaultMessage("Examination Conflict-Based Statistics")
4072+
@DoNotTranslate
4073+
String pageExaminationConflictBasedStatistics();
4074+
4075+
@DefaultMessage("Examination Assignment")
4076+
@DoNotTranslate
4077+
String pageExaminationAssignment();
40704078

40714079
@DefaultMessage("N/A")
40724080
String itemNotApplicable();
@@ -8119,4 +8127,7 @@ public interface GwtMessages extends Messages {
81198127

81208128
@DefaultMessage("Update")
81218129
String colUpdate();
8130+
8131+
@DefaultMessage("Student {0}")
8132+
String constraintStudent(String constraint);
81228133
}

0 commit comments

Comments
 (0)