Skip to content
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions backend/api/endpoints/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,45 @@ def get_commands(db: Session = Depends(get_db)):
items = db.exec(query).all()
return {"data": items}


@command_router.post("/", response_model=CommandSingleResponse)
def create_command(payload: CommandRequest):
def create_command(payload: CommandRequest, db: Session = Depends(get_db)):
"""
Creates an item with the given payload in the database and returns this payload after pulling it from the database

@param payload: The data used to create an item
@return returns a json object with field of "data" under which there is the payload now pulled from the database
"""
# TODO:(Member) Implement this endpoint


# Find number of commands in database
query = select(Command)
items = db.exec(query).all()

command = Command (**dict(payload))

db.add(command)
db.commit()
db.refresh(command)

return {"data": command}


@command_router.delete("/{id}", response_model=CommandListResponse)
def delete_command(id: int):
def delete_command(id: int, db: Session = Depends(get_db)):
"""
Deletes the item with the given id if it exists. Otherwise raises a 404 error.

@param id: The id of the item to delete
@return returns the list of commands after deleting the item
"""
# TODO:(Member) Implement this endpoint
command = db.get(Command, id)
if command is None:
raise HTTPException(status_code=404, detail="Item does not exist")

db.delete(command)
db.commit()

query2 = select(Command).where(Command.id != id)
items = db.exec(query2).all()
return {"data": items}
14 changes: 14 additions & 0 deletions backend/api/middlewares/logger_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

from backend.utils.logging import logger

from time import perf_counter
from datetime import datetime


class LoggerMiddleware(BaseHTTPMiddleware):
async def dispatch(
Expand All @@ -18,5 +23,14 @@ async def dispatch(
@return Response from endpoint
"""
# TODO:(Member) Finish implementing this method
logger.info(f'{request.method} {request.url.path}')
#logger.info(f"Request Parameters: {request.path_params}")
current_datetime = datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
logger.info(f"Datetime of request: {current_datetime}")

start = perf_counter()
response = await call_next(request)
duration = perf_counter() - start
logger.info(f"Duration: {duration} seconds")

return response
12 changes: 9 additions & 3 deletions backend/data/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ def validate_params_format(self):
The format of the comma seperated values is "data1,data2" so no spaces between data and the commas.
"""
# TODO: (Member) Implement this method
return self

if self.params is None and self.format is None:
return self
elif self.params is None or self.format is None:
raise ValueError("Parameters do not match required format")
elif len(self.params.split(",")) == len(self.format.split(",")):
return self
else:
raise ValueError("Parameters do not match required format")

class Command(BaseSQLModel, table=True):
"""
An instance of a MainCommand.
An instance off a MainCommand.
This table holds the data related to actual commands sent from the ground station up to the OBC.
"""

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/data/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface CommandRequest {
name: string
command_type: number,
params: string | null
format: string | null
//format: string | null
}

10 changes: 10 additions & 0 deletions frontend/src/display/command_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ export const getCommands = async (): Promise<CommandListResponse> => {
* @param id: command to delete
* @returns Promise<CommandListResponse>: list of commands after the command with the given id was deleted
*/

export const deleteCommand = async(id: number): Promise<CommandListResponse> => {
try {
const { data } = await axios.delete<CommandListResponse>(`${API_URL}/commands/${id}`)
return data;
} catch (error) {
console.error(error)
throw error
}
}
7 changes: 6 additions & 1 deletion frontend/src/display/table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react"
import { CommandResponse } from "../data/response"
import { getCommands } from "./command_api"
import { getCommands, deleteCommand } from "./command_api"
import CommandRow from "./row"

const CommandTable = () => {
Expand All @@ -19,7 +19,12 @@ const CommandTable = () => {
return () => {
// TODO: (Member) Handle delete logic here
// You will need to create a function in `command_api.ts` before you can finish this part.
const deleteCommandFn = async () => {
const data = await deleteCommand(id);
setCommands(data.data)
}

deleteCommandFn();
}
}

Expand Down
112 changes: 101 additions & 11 deletions frontend/src/input/command_input.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,120 @@
import "./command_input.css"
import { useState, useEffect } from "react"
import { MainCommandResponse } from "../data/response"
import { createCommand, getMainCommands } from "./input_api"


const CommandInput = () => {
// TODO: (Member) Setup state and useEffect calls here
const [mainCommands, setMainCommands] = useState<MainCommandResponse[]>([])
const [commandType, setCommandType] = useState<MainCommandResponse | null>(null)
const [params, setParams] = useState<Map<string, string>>(new Map());

useEffect(() => {
const setMainCommandsFn = async () => {
const data = await getMainCommands()
if(data.data.length == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use double equals == in comparisons. Use ===

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because == is loosely equal. Example:
0 == [] // true
0 == '0' //true
But:
[] == '0' // false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's even more cursed things about javascript:
0 < null // false
0 > null // false
0 == null // false
But
0 <= null // true
0 >= null // true
But if you were to replace all of the null above with undefined, they would all be false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can disregard the last 2 comments in the thread, they are just me showing you some trivia about js and why we use Typescript

alert("Error occurred. Please try again later.")
return
}
setMainCommands(data.data)
setCommandType(data.data[0])

const parameters = data.data[0].params?.split(",") || []
let paramsObject : Map<string, string> = new Map()

for(const param of parameters) {
paramsObject.set(param, "")
}
setParams(paramsObject);
}
setMainCommandsFn();
}, [])


const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
// TODO:(Member) Submit to your post endpoint
e.preventDefault();

//Check if parameters are valid
const allParams = commandType?.params?.split(",") || [];
const paramsList = []

const missingParams = []
for (const param of allParams) {
if (!params.get(param)) {
missingParams.push(param)
}
paramsList.push(params.get(param))
}
if(missingParams.length != 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use != in comparisons. Use !== instead

alert(`Parameters missing: ${missingParams.join(", ")}`)
return;
}


const reqBody = {
command_type: commandType?.id || 0,
params: paramsList.join(",")
};


const createCommandFn = async () => {
await createCommand(reqBody);
window.location.reload();
}
createCommandFn();
}

const changeCommandType = (e: React.ChangeEvent<HTMLSelectElement>) => {
const cmdType = mainCommands.find(cmd => cmd.id == +e.target.value)
if(!cmdType) {
alert("Error occurred. Please try again later.")
return
}
setCommandType(cmdType)
const parameters = cmdType?.params?.split(",") || []
let paramsObject : Map<string, string> = new Map()
for(const param of parameters) {
paramsObject.set(param, "")
}
setParams(paramsObject)
}

const changeParam = (e: React.ChangeEvent<HTMLInputElement>) => {
const param = e.target.id
const value = e.target.value

const handleSubmit = () => {
// TODO:(Member) Submit to your post endpoint
setParams(prevParams => {
const newParams = new Map(prevParams);
newParams.set(param, value);
return newParams;
})
}

return (
<>
<form>
<form onSubmit={handleSubmit}>
<div className="spreader">
<div>
<label>Command Type: </label>
<select>{/* TODO: (Member) Display the list of commands based on the get commands request*/}
<option value={"1"}>Command 1</option>
<option value={"2"}>Command 2</option>
<option value={"3"}>Command 3</option>
{/* TODO: (Member) Display the list of commands based on the get commands request*/}
<select
value={commandType?.id}
onChange={changeCommandType}>
{mainCommands.map(cmd => (<option value={cmd.id}>{cmd.name}</option>))}
</select>
</div>
<input /> {/* TODO: (Member) Add input handling here if the selected command has a param input*/}
<button onClick={handleSubmit}>Submit</button>

{/* TODO: (Member) Add input handling here if the selected command has a param input*/}
{commandType &&
commandType.params?.split(",").map((param) => (<input value={params.get(param)} id={param} placeholder={`Enter ${param}`} onChange={changeParam}/>))}

<button type="submit">Submit</button>
</div>
</form>
</>
</>
)
}

export default CommandInput;
export default CommandInput;
8 changes: 6 additions & 2 deletions test/backend/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from backend.data.enums import CommandStatus
from backend.utils.time import to_unix_time

from datetime import datetime


def test_get_commands(fastapi_test_client: TestClient, commands_json):
with fastapi_test_client as client:
Expand All @@ -24,8 +26,10 @@ def test_create_command(fastapi_test_client: TestClient):
assert result.get("status") == CommandStatus.PENDING.value
assert result.get("params") == "123456789"
# TODO: Figure out a better way to check the times
assert result.get("created_on")
assert result.get("updated_on")
created_on_dt = datetime.fromisoformat(result.get("created_on"))
updated_on_dt = datetime.fromisoformat(result.get("updated_on"))
assert isinstance(created_on_dt, datetime)
assert isinstance(updated_on_dt, datetime)
Comment on lines 28 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Not a part of the onboarding but definitely something that can be improved. Ill link this in the task to improve the gs onboarding.


def test_delete_command_fail(fastapi_test_client: TestClient):
with fastapi_test_client as client:
Expand Down