Skip to content

Commit

Permalink
Fix oppia#4386: Implement a CurrentInteractionService (oppia#5440)
Browse files Browse the repository at this point in the history
* Introduce CurrentInteractionService

* Add preSubmitHooks

* remove extra files introduced in merge conflict resolution

* oppia#2584-When adding a Link to a lesson, example URL is now a placeholder instead of default text

* Fix merge conflicts

* Remove accidental change to rich_text_components_definitions.js

* training

* solution editor fix

* Address other issues

* Handle case where NumberWithUnits form is not defined

* Add test and address comments

* Add test

* Address comments
  • Loading branch information
AllanYangZhou authored Aug 26, 2018
1 parent 26b53cf commit bd5a4d9
Show file tree
Hide file tree
Showing 37 changed files with 424 additions and 390 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,29 @@ oppia.directive('testInteractionPanel', [
scope: {
getStateName: '&stateName',
getInputTemplate: '&inputTemplate',
onSubmitAnswer: '&'
},
templateUrl: UrlInterpolationService.getDirectiveTemplateUrl(
'/pages/exploration_editor/editor_tab/' +
'test_interaction_modal_directive.html'),
controller: [
'$scope', 'ExplorationStatesService',
'INTERACTION_SPECS', 'INTERACTION_DISPLAY_MODE_INLINE',
'EVENT_PROGRESS_NAV_SUBMITTED',
'CurrentInteractionService',
function($scope, ExplorationStatesService,
INTERACTION_SPECS, INTERACTION_DISPLAY_MODE_INLINE,
EVENT_PROGRESS_NAV_SUBMITTED) {
CurrentInteractionService) {
var _stateName = $scope.getStateName();
var _state = ExplorationStatesService.getState(_stateName);
$scope.interactionIsInline = (
INTERACTION_SPECS[_state.interaction.id].display_mode ===
INTERACTION_DISPLAY_MODE_INLINE);
$scope.interactionAnswerIsValid = true;
$scope.submitAnswer = function(answer) {
$scope.onSubmitAnswer({
answer: answer
});
};

$scope.onSubmitAnswerFromButton = function() {
$scope.$broadcast(EVENT_PROGRESS_NAV_SUBMITTED);
CurrentInteractionService.submitAnswer();
};

$scope.setInteractionAnswerValidity = function(answerValidity) {
$scope.interactionAnswerIsValid = answerValidity;
};
$scope.isSubmitButtonDisabled = (
CurrentInteractionService.isSubmitButtonDisabled);
}
]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ oppia.factory('TrainingDataEditorPanelService', [
'ExplorationHtmlFormatterService', 'ResponsesService',
'StateCustomizationArgsService', 'TrainingDataService',
'TrainingModalService', 'FocusManagerService',
'CurrentInteractionService',
function($scope, $injector, $uibModalInstance, $filter,
ExplorationStatesService, StateEditorService, AlertsService,
AnswerClassificationService, ContextService,
StateInteractionIdService, AngularNameService,
EXPLICIT_CLASSIFICATION, TRAINING_DATA_CLASSIFICATION,
ExplorationHtmlFormatterService, ResponsesService,
StateCustomizationArgsService, TrainingDataService,
TrainingModalService, FocusManagerService) {
TrainingModalService, FocusManagerService,
CurrentInteractionService) {
var _explorationId = ContextService.getExplorationId();
var _stateName = StateEditorService.getActiveStateName();
$scope.stateName = _stateName;
Expand Down Expand Up @@ -154,6 +156,8 @@ oppia.factory('TrainingDataEditorPanelService', [
}
};

CurrentInteractionService.setOnSubmitFn($scope.submitAnswer);

$scope.openTrainUnresolvedAnswerModal = function(answerIndex) {
// An answer group must have either a rule or at least one
// answer in training data. Don't allow modification of training
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h3><[StateSolutionService.savedMemento !== null ? 'Update Solution' : 'Add Solu
<md-button class="oppia-learner-confirm-button protractor-test-submit-answer-button"
ng-click="onSubmitFromSubmitButton()"
ng-if="data.correctAnswer === null && shouldAdditionalSubmitButtonBeShown()"
ng-disabled="!answerIsValid">
ng-disabled="isSubmitButtonDisabled()">
Submit
</md-button>
<br>
Expand All @@ -40,7 +40,7 @@ <h3><[StateSolutionService.savedMemento !== null ? 'Update Solution' : 'Add Solu
<button class="btn btn-default" ng-click="cancel()">Cancel</button>
<button class="btn btn-success protractor-test-submit-solution-button"
ng-click="saveSolution()"
ng-disabled="data.correctAnswer === null || !data.explanationHtml || !answerIsValid">
ng-disabled="data.correctAnswer === null || !data.explanationHtml || isSubmitButtonDisabled()">
Check and Save Solution
</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<br>
<div class="submit-answer-btn">
<md-button class="oppia-learner-confirm-button protractor-test-submit-answer-button"
ng-disabled="!interactionAnswerIsValid"
ng-disabled="isSubmitButtonDisabled()"
ng-click="onSubmitAnswerFromButton()"
translate="I18N_INTERACTIONS_SUBMIT">
</md-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ <h3>Training Data</h3>
<div class="add-new-answer">
<p class="training-data-answer-label"> Add New Answer </p>
<test-interaction-panel input-template="inputTemplate"
on-submit-answer="submitAnswer(answer)"
state-name="stateName">
</test-interaction-panel>
<div ng-show="newAnswerIsAlreadyResolved">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
<script src="/templates/dev/head/pages/exploration_player/StateClassifierMappingService.js"></script>
<script src="/templates/dev/head/pages/exploration_player/PredictionAlgorithmRegistryService.js"></script>
<script src="/templates/dev/head/pages/exploration_player/PlaythroughService.js"></script>
<script src="/templates/dev/head/pages/exploration_player/CurrentInteractionService.js"></script>

<script src="/templates/dev/head/pages/exploration_player/InputResponsePairDirective.js"></script>
<script src="/templates/dev/head/pages/exploration_player/CorrectnessFooterDirective.js"></script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ oppia.directive('conversationSkin', [
'PretestQuestionBackendApiService', 'StateCardObjectFactory',
'CONTENT_FOCUS_LABEL_PREFIX', 'TWO_CARD_THRESHOLD_PX',
'CONTINUE_BUTTON_FOCUS_LABEL', 'EVENT_ACTIVE_CARD_CHANGED',
'EVENT_NEW_CARD_AVAILABLE', 'EVENT_PROGRESS_NAV_SUBMITTED',
'EVENT_NEW_CARD_AVAILABLE',
'FatigueDetectionService', 'NumberAttemptsService',
'PlayerCorrectnessFeedbackEnabledService', 'ContextService',
'ConceptCardBackendApiService', 'ConceptCardObjectFactory',
Expand All @@ -271,6 +271,7 @@ oppia.directive('conversationSkin', [
'PlaythroughService', 'PretestEngineService',
'WHITELISTED_COLLECTION_IDS_FOR_SAVING_GUEST_PROGRESS',
'ExplorationPlayerStateService', 'INTERACTION_DISPLAY_MODE_INLINE',
'CurrentInteractionService',
function(
$scope, $timeout, $rootScope, $window, $translate, $http,
$location, $q, MessengerService, AlertsService,
Expand All @@ -283,7 +284,7 @@ oppia.directive('conversationSkin', [
PretestQuestionBackendApiService, StateCardObjectFactory,
CONTENT_FOCUS_LABEL_PREFIX, TWO_CARD_THRESHOLD_PX,
CONTINUE_BUTTON_FOCUS_LABEL, EVENT_ACTIVE_CARD_CHANGED,
EVENT_NEW_CARD_AVAILABLE, EVENT_PROGRESS_NAV_SUBMITTED,
EVENT_NEW_CARD_AVAILABLE,
FatigueDetectionService, NumberAttemptsService,
PlayerCorrectnessFeedbackEnabledService, ContextService,
ConceptCardBackendApiService, ConceptCardObjectFactory,
Expand All @@ -295,7 +296,8 @@ oppia.directive('conversationSkin', [
StateClassifierMappingService, ImagePreloaderService,
PlaythroughService, PretestEngineService,
WHITELISTED_COLLECTION_IDS_FOR_SAVING_GUEST_PROGRESS,
ExplorationPlayerStateService, INTERACTION_DISPLAY_MODE_INLINE) {
ExplorationPlayerStateService, INTERACTION_DISPLAY_MODE_INLINE,
CurrentInteractionService) {
$scope.CONTINUE_BUTTON_FOCUS_LABEL = CONTINUE_BUTTON_FOCUS_LABEL;
// The minimum width, in pixels, needed to be able to show two cards
// side-by-side.
Expand Down Expand Up @@ -844,12 +846,14 @@ oppia.directive('conversationSkin', [
$scope.answerIsCorrect = false;
$scope.showPendingCard();
}
CurrentInteractionService.clearPresubmitHooks();
}
$scope.answerIsBeingProcessed = false;
}, millisecsLeftToWait);
}
);
};
CurrentInteractionService.setOnSubmitFn($scope.submitAnswer);
$scope.startCardChangeAnimation = false;
$scope.showPendingCard = function() {
$scope.startCardChangeAnimation = true;
Expand Down Expand Up @@ -1032,12 +1036,7 @@ oppia.directive('conversationSkin', [
$scope.explorationId);
};

// Interaction answer validity is used to enable/disable
// the progress-nav's Submit button. This logic is here because
// Interactions and the progress-nav are both descendants
// of ConversationSkinDirective.
$scope.interactionAnswerIsValid = true;
$scope.setInteractionAnswerValidity = function(answerValidity) {
$scope.isSubmitButtonDisabled = function() {
var currentIndex = PlayerPositionService.getDisplayedCardIndex();
// This check is added because it was observed that when returning
// to current card after navigating through previous cards, using
Expand All @@ -1046,13 +1045,13 @@ oppia.directive('conversationSkin', [
// card, this additional check doesn't interfere with its normal
// working.
if (!PlayerTranscriptService.isLastCard(currentIndex)) {
return;
return false;
}
$scope.interactionAnswerIsValid = answerValidity;
return CurrentInteractionService.isSubmitButtonDisabled();
};

$scope.submitAnswerFromProgressNav = function() {
$scope.$broadcast(EVENT_PROGRESS_NAV_SUBMITTED);
CurrentInteractionService.submitAnswer();
};
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2018 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Facilitates communication between the current interaction
* and the progress nav. The former holds data about the learner's answer,
* while the latter contains the actual "Submit" button which triggers the
* answer submission process.
*/

oppia.factory('CurrentInteractionService', [
function() {
var _submitAnswerFn = null;
var _onSubmitFn = null;
var _validityCheckFn = null;
var _presubmitHooks = [];

return {
setOnSubmitFn: function(onSubmit) {
/**
* The ConversationSkinDirective should register its onSubmit
* callback here.
*
* @param {function(answer, interactionRulesService)} onSubmit
*/
_onSubmitFn = onSubmit;
},
registerCurrentInteraction: function(submitAnswerFn, validityCheckFn) {
/**
* Each interaction directive should call registerCurrentInteraction
* when the interaction directive is first created.
*
* @param {function|null} submitAnswerFn - Should grab the learner's
* answer and pass it to onSubmit. The interaction can pass in
* null if it does not use the progress nav's submit button
* (ex: MultipleChoiceInput).
* @param {function} validityCheckFn - The progress nav will use this
* to decide whether or not to disable the submit button. If the
* interaction passes in null, the submit button will remain
* enabled (for the entire duration of the current interaction).
*/
_submitAnswerFn = submitAnswerFn || null;
_validityCheckFn = validityCheckFn || null;
},
registerPresubmitHook: function(hookFn) {
/* Register a hook that will be called right before onSubmit.
* All hooks for the current interaction will be cleared right
* before loading the next card.
*/
_presubmitHooks.push(hookFn);
},
clearPresubmitHooks: function() {
/* Clear out all the hooks for the current interaction. Should
* be called before loading the next card.
*/
_presubmitHooks = [];
},
onSubmit: function(answer, interactionRulesService) {
for (var i = 0; i < _presubmitHooks.length; i++) {
_presubmitHooks[i]();
}
_onSubmitFn(answer, interactionRulesService);
},
submitAnswer: function() {
/* This starts the answer submit process, it should be called once the
* learner presses the "Submit" button.
*/
if (_submitAnswerFn === null) {
throw Error('The current interaction did not ' +
'register a _submitAnswerFn.');
} else {
_submitAnswerFn();
}
},
isSubmitButtonDisabled: function() {
/* Returns whether or not the Submit button should be disabled based on
* the validity of the current answer. If the interaction does not pass
* in a _validityCheckFn, then _validityCheckFn will be null and by
* default we assume the answer is valid, so the submit button should
* not be disabled.
*/
if (_validityCheckFn === null) {
return false;
}
return !_validityCheckFn();
},
};
}
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2018 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Unit tests for CurrentInteractionService.
*/

describe('Current Interaction Service', function() {
beforeEach(module('oppia'));

var DUMMY_ANSWER = 'dummy_answer';

var CurrentInteractionService;
beforeEach(inject(function($injector) {
CurrentInteractionService = $injector.get('CurrentInteractionService');
}));

it('should properly register onSubmitFn and submitAnswerFn', function() {
var answerState = null;
var dummyOnSubmitFn = function(answer, interactionRulesService) {
answerState = answer;
};

CurrentInteractionService.setOnSubmitFn(dummyOnSubmitFn);
CurrentInteractionService.onSubmit(DUMMY_ANSWER, null);
expect(answerState).toEqual(DUMMY_ANSWER);

answerState = null;
var dummySubmitAnswerFn = function() {
CurrentInteractionService.onSubmit(DUMMY_ANSWER, null);
};
CurrentInteractionService.registerCurrentInteraction(
dummySubmitAnswerFn, null);
CurrentInteractionService.submitAnswer();
expect(answerState).toEqual(DUMMY_ANSWER);
});

it('should properly register validityCheckFn', function() {
var dummyValidityCheckFn = function() {
return false;
};
CurrentInteractionService.registerCurrentInteraction(
null, dummyValidityCheckFn);
expect(CurrentInteractionService.isSubmitButtonDisabled()).toBe(
!dummyValidityCheckFn());
});

it('should handle case where validityCheckFn is null', function() {
CurrentInteractionService.registerCurrentInteraction(null, null);
expect(CurrentInteractionService.isSubmitButtonDisabled()).toBe(false);
});

it('should properly register and clear presubmit hooks', function() {
var hookStateA = 0;
var hookStateB = 1;
var hookA = function() {
hookStateA = hookStateA + 1;
};
var hookB = function() {
hookStateB = hookStateB * 3;
};

CurrentInteractionService.registerPresubmitHook(hookA);
CurrentInteractionService.registerPresubmitHook(hookB);

CurrentInteractionService.setOnSubmitFn(function() {});
CurrentInteractionService.onSubmit(null, null);

expect(hookStateA).toEqual(1);
expect(hookStateB).toEqual(3);

CurrentInteractionService.clearPresubmitHooks();
CurrentInteractionService.onSubmit(null, null);

expect(hookStateA).toEqual(1);
expect(hookStateB).toEqual(3);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ oppia.constant(
'/createhandler/translate/<exploration_id>');


oppia.constant('EVENT_PROGRESS_NAV_SUBMITTED', 'progress-nav-submit');

/* This should match the CSS class defined in the tutor card directive. */
oppia.constant(
'AUDIO_HIGHLIGHT_CSS_CLASS', 'conversation-skin-audio-highlight');
Loading

0 comments on commit bd5a4d9

Please sign in to comment.