Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major goal- and step-loading refactor. #1004

Merged
merged 11 commits into from
Feb 10, 2021
2 changes: 1 addition & 1 deletion Backend.Tests/Controllers/UserEditControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void TestGoalToUserEdit()

// Create wrapper object
const int modGoalIndex = 0;
var wrapperObj = new UserEditObjectWrapper(modGoalIndex, stringUserEdit);
var wrapperObj = new UserEditStepWrapper(modGoalIndex, stringUserEdit);

_ = _userEditController.Put(_projId, origUserEdit.Id, wrapperObj);

Expand Down
36 changes: 27 additions & 9 deletions Backend/Controllers/UserEditController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,12 @@ public async Task<IActionResult> Post(string projectId, string userEditId, [From
return new NotFoundObjectResult(editIndex);
}

/// <summary> Adds a step to specified goal </summary>
/// <summary> Adds/updates a step to/in specified goal </summary>
/// <remarks> PUT: v1/projects/{projectId}/useredits/{userEditId} </remarks>
/// <returns> Index of newest edit </returns>
/// <returns> Index of added/modified step in specified goal </returns>
[HttpPut("{userEditId}")]
public async Task<IActionResult> Put(string projectId, string userEditId,
[FromBody] UserEditObjectWrapper userEdit)
[FromBody] UserEditStepWrapper stepEdit)
{
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
Expand All @@ -208,22 +208,40 @@ public async Task<IActionResult> Put(string projectId, string userEditId,
return new NotFoundObjectResult(projectId);
}

// Ensure userEdit exists
// Ensure userEdit exists.
var document = await _repo.GetUserEdit(projectId, userEditId);
if (document is null)
{
return new NotFoundResult();
}

// Ensure index exists
if (userEdit.GoalIndex >= document.Edits.Count)
// Ensure indices exist.
if (stepEdit.GoalIndex < 0 || stepEdit.GoalIndex >= document.Edits.Count)
{
return new BadRequestObjectResult("Goal index out of range.");
}
Console.WriteLine(stepEdit.StepIndex);
var maxStepIndex = document.Edits[stepEdit.GoalIndex].StepData.Count;
var stepIndex = stepEdit.StepIndex ?? maxStepIndex;
Console.WriteLine(stepIndex);
if (stepIndex < 0 || stepIndex > maxStepIndex)
{
return new BadRequestObjectResult("Goal index out of range");
return new BadRequestObjectResult("Step index out of range.");
}

await _userEditService.AddStepToGoal(projectId, userEditId, userEdit.GoalIndex, userEdit.NewEdit);
// Add new step to or update step in goal.
if (stepIndex == maxStepIndex)
{
await _userEditService.AddStepToGoal(
projectId, userEditId, stepEdit.GoalIndex, stepEdit.StepString);
}
else
{
await _userEditService.UpdateStepInGoal(
projectId, userEditId, stepEdit.GoalIndex, stepEdit.StepString, stepIndex);
}

return new OkObjectResult(document.Edits[userEdit.GoalIndex].StepData.Count - 1);
return new OkObjectResult(stepIndex);
}

/// <summary> Deletes <see cref="UserEdit"/> with specified id </summary>
Expand Down
3 changes: 2 additions & 1 deletion Backend/Interfaces/IUserEditService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace BackendFramework.Interfaces
{
public interface IUserEditService
{
Task<bool> AddStepToGoal(string projectId, string userEditId, int goalIndex, string userEdit);
Task<Tuple<bool, int>> AddGoalToUserEdit(string projectId, string userEditId, Edit edit);
Task<bool> AddStepToGoal(string projectId, string userEditId, int goalIndex, string stepString);
Task<bool> UpdateStepInGoal(string projectId, string userEditId, int goalIndex, string stepString, int stepIndex);
}
}
21 changes: 13 additions & 8 deletions Backend/Models/UserEdit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,33 +67,38 @@ public override int GetHashCode()
}
}

public class UserEditObjectWrapper
public class UserEditStepWrapper
{
[BsonElement("goalIndex")]
public int GoalIndex { get; set; }

[BsonElement("newEdit")]
public string NewEdit { get; set; }
[BsonElement("stepString")]
public string StepString { get; set; }

public UserEditObjectWrapper(int goalIndex, string newEdit)
[BsonElement("stepIndex")]
public int? StepIndex { get; set; }

public UserEditStepWrapper(int goalIndex, string stepString, int? stepIndex = null)
{
GoalIndex = goalIndex;
NewEdit = newEdit;
StepString = stepString;
StepIndex = stepIndex;
}

public override bool Equals(object? obj)
{
if (!(obj is UserEditObjectWrapper other) || GetType() != obj.GetType())
if (!(obj is UserEditStepWrapper other) || GetType() != obj.GetType())
{
return false;
}

return other.GoalIndex.Equals(GoalIndex) && other.NewEdit.Equals(NewEdit);
return other.GoalIndex.Equals(GoalIndex)
&& other.StepString.Equals(StepString) && other.StepIndex.Equals(StepIndex);
}

public override int GetHashCode()
{
return HashCode.Combine(GoalIndex, NewEdit);
return HashCode.Combine(GoalIndex, StepString, StepIndex);
}
}

Expand Down
36 changes: 27 additions & 9 deletions Backend/Services/UserEditApiServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public UserEditService(IUserEditRepository repo)
public async Task<Tuple<bool, int>> AddGoalToUserEdit(string projectId, string userEditId, Edit edit)
{
// Get userEdit to change
var userEntry = await _repo.GetUserEdit(projectId, userEditId);
var oldUserEdit = await _repo.GetUserEdit(projectId, userEditId);
const int invalidEditIndex = -1;
var failureResult = new Tuple<bool, int>(false, invalidEditIndex);
if (userEntry is null)
if (oldUserEdit is null)
{
return failureResult;
}

var newUserEdit = userEntry.Clone();
var newUserEdit = oldUserEdit.Clone();

// Add the new goal index to Edits list
newUserEdit.Edits.Add(edit);
Expand All @@ -54,18 +54,36 @@ public async Task<Tuple<bool, int>> AddGoalToUserEdit(string projectId, string u
return new Tuple<bool, int>(replaceSucceeded, indexOfNewestEdit);
}

/// <summary> Adds a string representation of a step to a <see cref="Edit"/> at a specified index </summary>
/// <summary> Adds a string representation of a step to a specified <see cref="Edit"/> </summary>
/// <returns> A bool: success of operation </returns>
public async Task<bool> AddStepToGoal(string projectId, string userEditId, int goalIndex, string userEdit)
public async Task<bool> AddStepToGoal(string projectId, string userEditId, int goalIndex, string newStep)
{
var addUserEdit = await _repo.GetUserEdit(projectId, userEditId);
if (addUserEdit is null)
var oldUserEdit = await _repo.GetUserEdit(projectId, userEditId);
if (oldUserEdit is null || goalIndex >= oldUserEdit.Edits.Count)
{
return false;
}

var newUserEdit = addUserEdit.Clone();
newUserEdit.Edits[goalIndex].StepData.Add(userEdit);
var newUserEdit = oldUserEdit.Clone();
newUserEdit.Edits[goalIndex].StepData.Add(newStep);
var updateResult = await _repo.Replace(projectId, userEditId, newUserEdit);
return updateResult;
}

/// <summary> Updates a specified step to in a specified <see cref="Edit"/> </summary>
/// <returns> A bool: success of operation </returns>
public async Task<bool> UpdateStepInGoal(
string projectId, string userEditId, int goalIndex, string updatedStep, int stepIndex)
{
var oldUserEdit = await _repo.GetUserEdit(projectId, userEditId);
if (oldUserEdit is null || goalIndex >= oldUserEdit.Edits.Count
|| stepIndex >= oldUserEdit.Edits[goalIndex].StepData.Count)
{
return false;
}

var newUserEdit = oldUserEdit.Clone();
newUserEdit.Edits[goalIndex].StepData[stepIndex] = updatedStep;
var updateResult = await _repo.Replace(projectId, userEditId, newUserEdit);
return updateResult;
}
Expand Down
17 changes: 10 additions & 7 deletions src/backend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";

import authHeader from "components/Login/AuthHeaders";
import history, { Path } from "browserHistory";
import { Goal } from "types/goals";
import { Goal, GoalStep } from "types/goals";
import { Project } from "types/project";
import { RuntimeConfig } from "types/runtimeConfig";
import SemanticDomainWithSubdomains from "types/SemanticDomain";
Expand Down Expand Up @@ -400,10 +400,11 @@ export async function avatarSrc(userId: string): Promise<string> {
return `data:${resp.headers["content-type"].toLowerCase()};base64,${image}`;
}

/** Returns index of added goal */
export async function addGoalToUserEdit(
userEditId: string,
goal: Goal
): Promise<Goal> {
): Promise<number> {
const stepData = JSON.stringify(goal.steps);
const userEditTuple = {
goalType: goal.goalType.toString(),
Expand All @@ -420,17 +421,19 @@ export async function addGoalToUserEdit(
return resp.data;
}

/** Returns index of step within specified goal */
export async function addStepToGoal(
userEditId: string,
goalIndex: number,
goal: Goal
): Promise<Goal> {
const newEdit = JSON.stringify(goal.steps);
const userEditTuple = { goalIndex, newEdit };
step: GoalStep,
stepIndex?: number // If undefined, step will be added to end.
): Promise<number> {
const stepString = JSON.stringify(step);
const stepEditTuple = { goalIndex, stepString, stepIndex };
return await backendServer
.put(
`projects/${LocalStorage.getProjectId()}/useredits/${userEditId}`,
userEditTuple,
stepEditTuple,
{
headers: { ...authHeader() },
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { Provider } from "react-redux";
import renderer, {
ReactTestInstance,
Expand Down
7 changes: 4 additions & 3 deletions src/components/GoalTimeline/GoalDisplay/HorizontalDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { Translate } from "react-localize-redux";
import { Typography, GridList, GridListTile, Button } from "@material-ui/core";

import { Goal } from "types/goals";
import { Goal, GoalType } from "types/goals";

const style = {
container: {
Expand All @@ -23,7 +23,7 @@ interface HorizontalDisplayProps {
width: number;
numPanes: number;
scrollToEnd: boolean;
handleChange: (name: string) => void;
handleChange: (goalType: GoalType) => void;
}

export default class HorizontalDisplay extends React.Component<HorizontalDisplayProps> {
Expand All @@ -47,8 +47,9 @@ export default class HorizontalDisplay extends React.Component<HorizontalDisplay
{ ...style.buttonStyle, width: this.optionWidth + "vw" } as any
}
onClick={() => {
this.props.handleChange(goal.name);
this.props.handleChange(goal.goalType);
}}
disabled={goal.completed}
>
<Typography variant={"h6"}>
<Translate id={goal.name + ".title"} />
Expand Down
7 changes: 4 additions & 3 deletions src/components/GoalTimeline/GoalDisplay/VerticalDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Typography, GridList, GridListTile, Button } from "@material-ui/core";
import React from "react";
import { Translate } from "react-localize-redux";

import { Goal } from "types/goals";
import { Goal, GoalType } from "types/goals";

const style = {
container: {
Expand All @@ -23,7 +23,7 @@ interface VerticalDisplayProps {
height: number;
numPanes: number;
scrollToEnd: boolean;
handleChange: (name: string) => void;
handleChange: (goalType: GoalType) => void;
}

interface VerticalDisplayStates {
Expand Down Expand Up @@ -54,9 +54,10 @@ export default class VerticalDisplay extends React.Component<
{ ...style.buttonStyle, height: this.optionHeight + "vw" } as any
}
onClick={() => {
this.props.handleChange(goal.name);
this.props.handleChange(goal.goalType);
}}
fullWidth
disabled={goal.completed}
>
<Typography variant={"h6"}>
<Translate id={goal.name + ".title"} />
Expand Down
51 changes: 19 additions & 32 deletions src/components/GoalTimeline/GoalTimelineComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Translate } from "react-localize-redux";

import HorizontalDisplay from "components/GoalTimeline/GoalDisplay/HorizontalDisplay";
import VerticalDisplay from "components/GoalTimeline/GoalDisplay/VerticalDisplay";
import { Goal } from "types/goals";
import { Goal, GoalType } from "types/goals";
import { goalTypeToGoal } from "types/goalUtilities";

const timelineStyle = {
centerDisplays: {
Expand Down Expand Up @@ -78,58 +79,44 @@ export default class GoalTimeline extends React.Component<

// Given a change event, find which goal the user selected, and choose it
// as the next goal to work on.
handleChange(name: string) {
const goal = this.props.allPossibleGoals.find((goal) => goal.name === name);
if (goal) {
this.props.chooseGoal(goal);
}
handleChange(goalType: GoalType) {
// Create a new goal to prevent mutating the suggestions list.
this.props.chooseGoal(goalTypeToGoal(goalType));
}

// Creates a list of suggestions, with non-suggested goals at the end and
// our main suggestion absent (to be displayed on the suggestions button)
createSuggestionData(): Goal[] {
let data: Goal[] = this.props.suggestions.slice(1);
let hasGoal: boolean;

if (this.props.suggestions.length) {
for (let goal of this.props.allPossibleGoals) {
hasGoal = false;
for (let i = 0; i < data.length && !hasGoal; i++) {
if (goal.name === data[i].name) {
hasGoal = true;
}
}
if (!hasGoal && goal.name !== this.props.suggestions[0].name) {
data.push(goal);
}
}
} else {
const suggestions = this.props.suggestions;
if (!suggestions.length) {
return this.props.allPossibleGoals;
}
return data;
const secondarySuggestions = suggestions.slice(1);
const suggestionTypes = suggestions.map((g) => g.goalType);
const nonSuggestions = this.props.allPossibleGoals.filter(
(g) => !suggestionTypes.includes(g.goalType)
);
secondarySuggestions.push(...nonSuggestions);
return secondarySuggestions;
}

// Creates a button for our recommended goal
goalButton(): ReactElement {
let done: boolean = this.props.suggestions.length === 0;
const done = this.props.suggestions.length === 0;
// Create a new goal to prevent mutating the suggestions list.
const goal = goalTypeToGoal(this.props.suggestions[0]?.goalType);
return (
<Button
style={timelineStyle.centerButton as any}
color={"primary"}
variant={"contained"}
disabled={done}
onClick={() => {
this.props.chooseGoal(this.props.suggestions[0]);
this.props.chooseGoal(goal);
}}
>
<Typography variant={"h4"}>
<Translate
id={
done
? "goal.selector.done"
: this.props.suggestions[0].name + ".title"
}
/>
<Translate id={done ? "goal.selector.done" : goal.name + ".title"} />
</Typography>
</Button>
);
Expand Down
Loading