private (bool, string) FormSubmitHandler(User user, UserFormSubmission submission) { if (submission?.SubForms?.Count == ObjectList.Count) { for (int i = 0; i < submission.SubForms.Count; i++) { T extractedAnswer = AnswerExtractor(submission.SubForms[i]); if (extractedAnswer != null) { if (ObjectList[i].UserAnswers == null) { ObjectList[i].UserAnswers = new List <QueryInfo <T> >(); } ObjectList[i].UserAnswers.Add(new QueryInfo <T>() { UserQueried = user, TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds, Answer = extractedAnswer, }); } } } return(true, string.Empty); }
/// <summary> /// This is the outlet function that Exit will call /// </summary> private void Outlet(User user, UserStateResult result, UserFormSubmission input) { if (this.FirstUser) { lock (this.FirstUserLock) { if (this.FirstUser) { foreach (var listener in this.StateEndingListeners) { listener?.Invoke(); } this.FirstUser = false; } } } lock (user) { foreach (var listener in this.PerUserStateEndingListeners) { listener?.Invoke(user); } } if (this.UserOutletOverrides.TryGetValue(user, out Connector connector)) { connector(user, result, input); } else { this.InternalOutlet(user, result, input); } }
public async Task SubmitUserForm(UserPrompt prompt, UserFormSubmission submission, string userId) { if (submission == null) { return; } submission.Id = prompt.Id; for (int i = 0; i < (submission.SubForms?.Count ?? 0); i++) { submission.SubForms[i].Id = prompt.SubPrompts?[i]?.Id ?? Guid.Empty; } string path = Constants.Path.FormSubmit; // If set up to auto submit and randomly selected to auto submit. if ((prompt.AutoSubmitAtTime != null) && (Rand.NextDouble() < this.AutoSubmitPercentage)) { path = Constants.Path.AutoFormSubmit; } HttpResponseMessage httpResponseMessage = await MakeWebRequest( path: path, userId : userId, method : HttpMethod.Post, content : new StringContent( JsonConvert.SerializeObject(submission), Encoding.UTF8, Constants.MediaType.ApplicationJson)); await httpResponseMessage.ThrowIfNonSuccessResponse(userId); }
public void Inlet(User user, UserStateResult result, UserFormSubmission formSubmission) { if (!this.UsersInLobby.ContainsKey(user.Id)) { throw new Exception("User not registered for this lobby"); } this.WaitForLobbyStart.Inlet(user, result, formSubmission); }
public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter) { this.Drawings.Add(new PeopleUserDrawing { Drawing = input.SubForms[0].Drawing, Owner = user, Type = UsersToRandomizedBodyPartTypes[user][counter % 3] }); return(true, string.Empty); }
public IActionResult Post( [FromBody] UserFormSubmission formData, string id) { if (!ModelState.IsValid) { return(new BadRequestResult()); } if (!Sanitize.SanitizeString(id, out string error, Constants.RegexStrings.UserId, 50, 50)) { return(BadRequest(error)); } User user = GameManager.MapIdentifierToUser(id, out bool newUser); if (user != null) { user.LastSubmitTime = DateTime.UtcNow; } if (user?.UserState == null || newUser) { return(BadRequest("Error finding user object, try again.")); } if (!Sanitize.SanitizeAllStrings(formData, out error)) { return(BadRequest(error)); } // Make sure HandleUserFormInput is never called concurrently for the same user. bool success = false; try { lock (user.LockObject) { // If user form input was valid, handle it, else return the error. if (user.UserState.CleanUserFormInput(user, ref formData, out error) == UserState.CleanUserFormInputResult.Valid) { success = user.UserState.HandleUserFormInput(user, formData, out error); } } } catch (Exception e) { error = "An unexpected error occurred, refresh and try again :("; success = false; // Let GameManager know so it can determine whether or not to abandon the lobby. GameManager.ReportGameError(ErrorType.UserSubmit, user?.LobbyId, user, e); } return(success ? new OkResult() : BadRequest(error)); }
public IActionResult Post( [FromBody] UserFormSubmission formData, string id) { if (!Sanitize.SanitizeString(id, out string error, Constants.RegexStrings.UserId, 50, 50)) { return(BadRequest(error)); } User user = GameManager.MapIdentifierToUser(id, out bool newUser); if (user != null) { user.LastSubmitTime = DateTime.UtcNow; } if (user?.UserState == null || newUser) { return(BadRequest("Error finding user object, try again.")); } if (!Sanitize.SanitizeAllStrings(formData, out error)) { return(BadRequest(error)); } // Make sure HandleUserFormInput is never called concurrently for the same user. try { lock (user.LockObject) { if (!user.UserState.UserRequestingCurrentPrompt(user).SubmitButton) { return(BadRequest("You shouldn't be able to time out on this prompt.")); } // Cleans invalid fields and replaces with null. if (user.UserState.CleanUserFormInput(user, ref formData, out error) != UserState.CleanUserFormInputResult.Invalid) { // Handles partial/null inputs. user.UserState.HandleUserTimeout(user, formData); } } } catch (Exception e) { error = "An unexpected error occurred, refresh and try again :("; // Let GameManager know so it can determine whether or not to abandon the lobby. GameManager.ReportGameError(ErrorType.UserSubmit, user?.LobbyId, user, e); return(new BadRequestObjectResult(error)); } return(new OkResult()); }
public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter) { if (input?.SubForms?.Count == 3 && input.SubForms[0].ShortAnswer != null && input.SubForms[1].ShortAnswer != null && input.SubForms[2].ShortAnswer != null) { HandleInput(user, input); } return(UserTimeoutAction.None); }
public async Task GetPromptAndSubmitUserForm(Func <UserPrompt, UserFormSubmission> handler, string userId) { HttpResponseMessage currentContentResponse = await MakeWebRequest( path : Constants.Path.CurrentContent, userId : userId, method : HttpMethod.Get); UserPrompt prompt = JsonConvert.DeserializeObject <UserPrompt>(await currentContentResponse.Content.ReadAsStringAsync()); UserFormSubmission submission = handler(prompt); await SubmitUserForm(prompt, submission, userId); }
public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter) { if (input?.SubForms?[0]?.Drawing != null) { Drawings.Add(new PeopleUserDrawing { Drawing = input.SubForms[0].Drawing, Owner = user, Type = UsersToRandomizedBodyPartTypes[user][counter % 3] }); } return(UserTimeoutAction.None); }
public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter) { if (input?.SubForms?[0]?.ShortAnswer != null && !Prompts.Select((prompt) => prompt.Text).Contains(input.SubForms[0].ShortAnswer)) { Prompts.Add( new Prompt() { Owner = user, Text = input.SubForms[0].ShortAnswer }); } return(UserTimeoutAction.None); }
public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter) { if (Prompts.Select((prompt) => prompt.Text).Contains(input.SubForms[0].ShortAnswer)) { return(false, "Someone has already entered that prompt"); } Prompts.Add( new Prompt() { Owner = user, Text = input.SubForms[0].ShortAnswer }); return(true, String.Empty); }
/// <summary> /// The inlet to the transition. /// </summary> /// <param name="user">The user to move into the transition.</param> /// <param name="stateResult">The state result of the last node (this transition doesnt care).</param> /// <param name="formSubmission">The user input of the last node (this transition doesnt care).</param> public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { base.Inlet(user, stateResult, formSubmission); if (firstUser) { lock (firstUserLock) { if (firstUser) { this.delayTriggerTask = DelayedTrigger(this.Delay); firstUser = false; } } } }
private T TryExtractObjectVotedFor(User user, UserFormSubmission submission) { SubPrompt subPrompt = this.PromptsPerUser[user].SubPrompts[0]; T objectVotedFor = null; if (subPrompt?.Answers != null && (submission?.SubForms?.FirstOrDefault()?.RadioAnswer != null)) { objectVotedFor = this.ObjectList[submission.SubForms.FirstOrDefault().RadioAnswer.Value]; } else if (subPrompt?.Selector?.ImageList != null && (submission?.SubForms?.FirstOrDefault()?.Selector != null)) { objectVotedFor = this.ObjectList[submission.SubForms[0].Selector.Value]; } return(objectVotedFor); }
/// <summary> /// The inlet to the transition. /// </summary> /// <param name="user">The user to move into the transition.</param> /// <param name="stateResult">The state result of the last node (this transition doesnt care).</param> /// <param name="formSubmission">The user input of the last node (this transition doesnt care).</param> public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { base.Inlet(user, stateResult, formSubmission); this.UsersWaiting.Add(user); // TODO: locks in this flow almost certainly have race conditions. // Will recalculate active users on each submission. Slight "bug" if last user becomes inactive we still wait out the timer. // Hurry users once all active have submitted. IFF that is the selected mode if (this.UsersToWaitForType == WaitForUsersType.Active && !this.Hurried && this.GetUsers(WaitForUsersType.Active).IsSubsetOf(this.UsersWaiting)) { lock (this.TriggeredLock) { // UsersToWaitForType cannot change. if (!this.Hurried && this.GetUsers(WaitForUsersType.Active).IsSubsetOf(this.UsersWaiting)) { this.Hurried = true; this.ParentState.HurryUsers(); } } } // Proceed to next state once we have all users. if (!this.Triggered && this.GetUsers(WaitForUsersType.All).IsSubsetOf(this.UsersWaiting)) { bool triggeringThread = false; lock (this.TriggeredLock) { if (!this.Triggered && this.GetUsers(WaitForUsersType.All).IsSubsetOf(this.UsersWaiting)) { this.Triggered = true; triggeringThread = true; } } // Cannot call this from within a lock, can only be called by one thread. Other threads will just go into // waiting mode :) if (triggeringThread) { this.Trigger(); } } }
public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { // If there is no submit button the user is not meant to answer prompts or hurry through. if (!AwaitingInput(user)) { return; } // Set user to answering prompts state if they arent being hurried and their current prompt has a submit button. if (user.StatesTellingMeToHurry.Count == 0) { user.Status = UserStatus.AnsweringPrompts; } else { // TODO: May need to pass in prompt rather than getting from user.UserState but should be okay for now. HandleUserTimeout(user, UserFormSubmission.WithNulls(user.UserState?.UserRequestingCurrentPrompt(user))); } }
private (bool, string) FormSubmitHandler(User user, UserFormSubmission submission) { T objectVotedFor = TryExtractObjectVotedFor(user, submission); objectVotedFor.Should().NotBeNull(because: "Could not find an object voted for"); VoteInfo vote = new VoteInfo { ObjectsVotedFor = new List <object> { objectVotedFor }, TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds, UserWhoVoted = user }; objectVotedFor.VotesCastForThisObject.Add(vote); this.UserVotes[user] = vote; return(true, string.Empty); }
private void HandleInput(User user, UserFormSubmission input) { Question question; int leftLabelInt; int rightLabelInt; string leftLabel = input?.SubForms[1]?.ShortAnswer; string rightLabel = input?.SubForms[2]?.ShortAnswer; if (int.TryParse(leftLabel, out leftLabelInt) && int.TryParse(rightLabel, out rightLabelInt) && leftLabelInt < rightLabelInt && rightLabelInt - leftLabelInt <= FriendQuizConstants.MaxSliderTickRange) { question = new Question() { Owner = user, Text = input.SubForms[0].ShortAnswer, MinBound = leftLabelInt, MaxBound = rightLabelInt, Numeric = true, TickLabels = new List <string>() { leftLabel, rightLabel } }; } else { question = new Question() { Owner = user, Text = input.SubForms[0].ShortAnswer, TickLabels = new List <string>() { leftLabel, rightLabel } }; } RoundTracker.Questions.Add(question); }
private UserTimeoutAction FormTimeoutHandler(User user, UserFormSubmission submission) { T objectVotedFor = TryExtractObjectVotedFor(user, submission); // TODO: Support ability to not count a vote. if (objectVotedFor == null) { objectVotedFor = ObjectList.First(); } VoteInfo vote = new VoteInfo { ObjectsVotedFor = new List <object> { objectVotedFor }, TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds, UserWhoVoted = user }; objectVotedFor.VotesCastForThisObject.Add(vote); this.UserVotes[user] = vote; return(UserTimeoutAction.None); }
public static CleanUserFormInputResult CleanUserFormInput(UserPrompt userPrompt, ref UserFormSubmission userInput, out string error) { // No data submitted / requested if (userInput == null || userPrompt == null) { error = "Try again or try refreshing the page."; userInput = UserFormSubmission.WithNulls(userPrompt); return(CleanUserFormInputResult.Invalid); } // Old data submitted. if (userInput.Id != userPrompt.Id) { error = "Outdated form submitted, try again or try refreshing the page."; userInput = UserFormSubmission.WithNulls(userPrompt); return(CleanUserFormInputResult.Invalid); } // No prompts requested. if (userPrompt?.SubPrompts == null || userPrompt.SubPrompts.Length == 0) { userInput = UserFormSubmission.WithNulls(userPrompt); error = ""; return(CleanUserFormInputResult.Valid); } if (userInput?.SubForms == null || (userPrompt.SubPrompts.Length != userInput.SubForms.Count)) { error = "Error in submission, try again or try refreshing the page."; userInput = UserFormSubmission.WithNulls(userPrompt); return(CleanUserFormInputResult.Invalid); } int i = 0; CleanUserFormInputResult result = CleanUserFormInputResult.Valid; error = string.Empty; foreach (SubPrompt prompt in userPrompt?.SubPrompts ?? new SubPrompt[0]) { UserSubForm subForm = userInput.SubForms[i]; if (!(PromptInputValidation.Validate_Drawing(prompt, subForm) && PromptInputValidation.Validate_ShortAnswer(prompt, subForm) && PromptInputValidation.Validate_ColorPicker(prompt, subForm) && PromptInputValidation.Validate_Answers(prompt, subForm) && PromptInputValidation.Validate_Selector(prompt, subForm) && PromptInputValidation.Validate_Dropdown(prompt, subForm) && PromptInputValidation.Validate_Slider(prompt, subForm))) { error = "Not all form fields have been filled out"; result = CleanUserFormInputResult.Cleaned; // Invalid fields get set to null (used in autosubmit partial submission flows). userInput.SubForms[i] = new UserSubForm() { Id = prompt.Id }; } i++; } return(result); }
public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { this.Exit.Inlet(user, stateResult, formSubmission); }
public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { this.WaitingState.Inlet(user, stateResult, formSubmission); }
public CleanUserFormInputResult CleanUserFormInput(User user, ref UserFormSubmission userInput, out string error) { UserPrompt userPrompt = GetUserPromptHolder(user).Prompt; return(CleanUserFormInput(userPrompt, ref userInput, out error)); }
public abstract UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter);
public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { this.InternalConnector?.Invoke(user, stateResult, formSubmission); }
// TODO: configure delayBetweenSubmissions? - Command line arg. public virtual async Task RunTest() { int i = 0; const int maxIters = 500; // Reset data structures so that they always show current GameStep. Dictionary <LobbyPlayer, Task <UserPrompt> > playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >(); List <Task> playerSubmissions = new List <Task>(); try { for (i = 0; i < maxIters; i++) { // Reset data structures so that they always show current GameStep. playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >(); playerSubmissions = new List <Task>(); DateTime pollingEnd = DateTime.UtcNow.Add(this.MaxTotalPollingTime); // Keep polling current prompts while (!playerPrompts.Values.Any(val => val.Result.SubmitButton)) { if (DateTime.UtcNow > pollingEnd) { throw new Exception("Ran out of time polling, did game soft-lock?"); } // Delay not needed on first iteration. if (playerPrompts.Count > 0) { Thread.Sleep((int)this.PollingDelay.TotalMilliseconds); } playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >(); foreach (LobbyPlayer player in Lobby.Players) { playerPrompts.Add(player, this.WebClient.GetUserPrompt(player.UserId)); } await Task.WhenAll(playerPrompts.Values); } // HACKY FIX. Above is checking if ANY user has a prompt. To avoid race condition. wait a little and check everybody again. Thread.Sleep((int)this.PollingDelay.TotalMilliseconds); playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >(); foreach (LobbyPlayer player in Lobby.Players) { playerPrompts.Add(player, this.WebClient.GetUserPrompt(player.UserId)); } await Task.WhenAll(playerPrompts.Values); // END HACKY FIX var prompts = playerPrompts.Values.Select(val => val.Result); // Check if game end if (prompts.Any(prompt => prompt.UserPromptId == UserPromptId.PartyLeader_GameEnd)) { Console.WriteLine("Game Finished"); break; } // Validate current set is expected. if (this is IStructuredTest) { var validations = ((IStructuredTest)this).UserPromptIdValidations; if (i > validations.Count) { throw new Exception($"Game has gone past all structured test validations without ending."); } this.ValidatePrompts(validations[i], prompts); } foreach ((LobbyPlayer player, Task <UserPrompt> promptTask) in playerPrompts) { Thread.Sleep((int)this.DelayBetweenSubmissions.TotalMilliseconds); UserPrompt prompt = promptTask.Result; UserFormSubmission submission = HandleUserPrompt(prompt, player, i); bool providedSubmission = submission != null; if (!prompt.SubmitButton && providedSubmission) { throw new Exception($"Test's 'HandleUserPrompt' provided a UserFormSubmission when it was not expected. UserId='{player.UserId}'"); } if (prompt.SubmitButton && !providedSubmission) { throw new Exception($"Test's 'HandleUserPrompt' did not provide a UserFormSubmission when one was expected. UserId='{player.UserId}'"); } if (submission != null) { playerSubmissions.Add(this.WebClient.SubmitUserForm(prompt, submission, player.UserId)); } } await Task.WhenAll(playerSubmissions); // Validate game was expected to end here if (i >= maxIters) { throw new Exception($"Test runner exceeded ({i}) max game steps of ({maxIters})"); } } if ((this is IStructuredTest) && (i != ((IStructuredTest)this).UserPromptIdValidations.Count)) { throw new Exception($"Game ended unexpectedly. Expected ({((IStructuredTest)this).UserPromptIdValidations.Count}) game steps, actual:({i})"); } } catch (Exception e) { e.Data.Add(Constants.ExceptionDataKeys.GameStep, i); // Try and add additional data points try { if (this is IStructuredTest) { e.Data.Add(Constants.ExceptionDataKeys.Validations, ((IStructuredTest)this).UserPromptIdValidations.Select(var => var.PrettyPrint())); } e.Data.Add(Constants.ExceptionDataKeys.Prompts, $"*[{i}]:{SummarizePrompts(playerPrompts.Values.Select(task => task.Result)).PrettyPrint()}"); } catch { // Empty } throw; } }
public abstract (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter);
public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter) { HandleInput(user, input); return(true, string.Empty); }
public virtual void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission) { this.InvokeEntranceListeners(user); this.InternalOutletConnector(user, stateResult, formSubmission); }
/// <summary> /// This implementation validates user input, and should be overridden (with a call to base.HandlerUserFormInput) /// </summary> /// <param name="userInput">Validates the users form input and decides what UserStateResult to return to StatecompletedCallback.</param> /// <returns>True if the user input was accepted, false if there was an issue.</returns> public abstract bool HandleUserFormInput(User user, UserFormSubmission userInput, out string error);