Skip to content

Commit

Permalink
CHG update course to use theia image table for default image field
Browse files Browse the repository at this point in the history
  • Loading branch information
wabscale committed Nov 21, 2021
1 parent 20c1971 commit 54ac8f6
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 41 deletions.
3 changes: 3 additions & 0 deletions api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ yeetdb:
cleanyeetdb:
make -C .. cleanyeetdb

test: venv
./tests/test.sh

.PHONY: migrations # Run alembic migrations
migrations: venv
./venv/bin/alembic upgrade head
Expand Down
2 changes: 1 addition & 1 deletion api/anubis/lms/assignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def assignment_sync(assignment_data: dict) -> Tuple[Union[dict, str], bool]:
# Check if it exists
if assignment is None:
assignment = Assignment(
theia_image=course.theia_default_image,
theia_image_id=course.theia_default_image_id,
theia_options=course.theia_default_options,
unique_code=assignment_data["unique_code"],
course=course,
Expand Down
5 changes: 3 additions & 2 deletions api/anubis/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class Course(db.Model):
default="https://github.com/os3224/anubis-assignment-tests",
)
github_repo_required = db.Column(db.Boolean, default=True)
theia_default_image = db.Column(db.TEXT, nullable=False, default="registry.digitalocean.com/anubis/theia-xv6")
theia_default_image_id = db.Column(db.String(128), db.ForeignKey('theia_image.id'), nullable=True)
theia_default_options = db.Column(MutableJson, default=lambda: copy.deepcopy(THEIA_DEFAULT_OPTIONS))
github_org = db.Column(db.TEXT, default="os3224")
join_code = db.Column(db.String(256), unique=True)
Expand Down Expand Up @@ -213,7 +213,7 @@ class Assignment(db.Model):

# IDE
ide_enabled = db.Column(db.Boolean, default=True)
theia_image_id = db.Column(db.String(128), db.ForeignKey('theia_image.id'))
theia_image_id = db.Column(db.String(128), db.ForeignKey('theia_image.id'), default=None)
theia_options = db.Column(MutableJson, default=lambda: copy.deepcopy(THEIA_DEFAULT_OPTIONS))

# Github
Expand Down Expand Up @@ -654,6 +654,7 @@ class TheiaImage(db.Model):
label = db.Column(db.String(1024), nullable=False, default='')
public = db.Column(db.Boolean, nullable=False, default=False)

courses = db.relationship('Course', backref='theia_default_image')
assignments = db.relationship('Assignment', backref='theia_image')
sessions = db.relationship('TheiaSession', backref='image')

Expand Down
7 changes: 3 additions & 4 deletions api/anubis/rpc/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ def seed():
student_user = User(netid="student", github_username="student", name="student")
db.session.add_all([superuser, professor_user, ta_user, student_user])

xv6_image = TheiaImage(
image="registry.digitalocean.com/anubis/theia-xv6"
)
db.session.add(xv6_image)
xv6_image = TheiaImage(image="registry.digitalocean.com/anubis/theia-xv6", label="theia-xv6")
admin_image = TheiaImage(image="registry.digitalocean.com/anubis/theia-admin", label="theia-admin")
db.session.add_all([xv6_image, admin_image])

db.session.commit()

Expand Down
3 changes: 3 additions & 0 deletions api/anubis/views/admin/assignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ def admin_assignments_save(assignment: dict):
db_assignment.theia_image_id = None
continue

if key == 'theia_image_id':
continue

setattr(db_assignment, key, value)

# Attempt to commit
Expand Down
18 changes: 17 additions & 1 deletion api/anubis/views/admin/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ def admin_courses_list():
:return:
"""

course_data = row2dict(course_context)
if course_context.theia_default_image_id is not None:
course_data['theia_default_image'] = course_context.theia_default_image.data
else:
course_data['theia_default_image'] = None

# Return the course context broken down
return success_response(
{
"course": row2dict(course_context),
"course": course_data,
}
)

Expand Down Expand Up @@ -101,10 +107,20 @@ def admin_courses_save_id(course: dict):
for column in Course.__table__.columns:
if column.name in course:
key, value = column.name, course[column.name]

if isinstance(value, str):
value = value.strip()

if key == 'theia_default_image_id':
continue

setattr(db_course, key, value)

if course['theia_default_image'] is not None:
db_course.theia_default_image_id = course['theia_default_image']['id']
else:
db_course.theia_default_image_id = None

# Commit the changes
try:
db.session.commit()
Expand Down
9 changes: 8 additions & 1 deletion api/anubis/views/admin/ide.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,19 @@ def admin_ide_initialize_custom(settings: dict, **_):
message="Starting new IDEs is currently disabled by an Anubis administrator. " "Please try again later.",
)

image_db = TheiaImage.query.filter(
TheiaImage.image == image
).first()

if image_db is None:
return error_response('Theia IDE Image is not yet registered')

# Create a new session
session = TheiaSession(
owner_id=current_user.id,
assignment_id=None,
course_id=course_context.id,
image=image,
image_id=image_db.id,
repo_url=repo_url,
active=True,
state="Initializing",
Expand Down
18 changes: 17 additions & 1 deletion api/migrations/versions/08c5273b4713_add_theia_image_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,23 @@ def upgrade():
op.drop_column("theia_session", "image")
op.create_foreign_key(
None, "theia_session", "theia_image", ["image_id"], ["id"]
)
)

# Handle course table
op.add_column(
"course",
sa.Column("theia_default_image_id", sa.String(length=128), nullable=True),
)
for i in images:
conn.execute(
sa.text('update course set theia_default_image_id = :id where theia_default_image = :image;'),
id=i['id'], image=i['image'],
)
op.drop_column("course", "theia_default_image")
op.create_foreign_key(
None, "course", "theia_image", ["theia_default_image_id"], ["id"]
)

# ### end Alembic commands ###


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@


def test_config_admin():
permission_test("/admin/config/list")
permission_test("/super/config/list")
permission_test(
"/admin/config/save",
"/super/config/save",
method="post",
json={"config": sample_config},
fail_for=[
Expand Down
92 changes: 67 additions & 25 deletions old-web/src/Components/Admin/Course/CourseCard.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import React, {useState} from 'react';
import axios from 'axios';
import {useSnackbar} from 'notistack';
import {Link} from 'react-router-dom';

import Grid from '@material-ui/core/Grid';
Expand All @@ -11,9 +13,12 @@ import CardContent from '@material-ui/core/CardContent';
import yellow from '@material-ui/core/colors/yellow';
import EditIcon from '@material-ui/icons/Edit';
import Switch from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Autocomplete from '@material-ui/lab/Autocomplete';

import AuthContext from '../../../Contexts/AuthContext';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import standardStatusHandler from '../../../Utils/standardStatusHandler';
import standardErrorHandler from '../../../Utils/standardErrorHandler';


const useStyles = makeStyles((theme) => ({
Expand All @@ -24,35 +29,72 @@ const useStyles = makeStyles((theme) => ({

export default function CourseCard({course, _disabled, editableFields, updateField, saveCourse}) {
const classes = useStyles();
const {enqueueSnackbar} = useSnackbar();
const [images, setImages] = useState([]);

React.useEffect(() => {
axios.get(`/api/admin/ide/images/list`).then((response) => {
const data = standardStatusHandler(response, enqueueSnackbar);
if (data.images) {
setImages(data.images);
}
}).catch(standardErrorHandler(enqueueSnackbar));
}, []);

return (
<Card>
<CardContent>
<Grid container spacing={2}>
{editableFields.map(({field, label, type = 'text', disabled = false}) => (
<Grid item xs={12} key={field}>
{type === 'text' && <TextField
fullWidth
disabled={disabled || _disabled}
variant={'outlined'}
label={label}
value={course[field]}
onChange={updateField(course.id, field)}
/>}
{type === 'boolean' && <FormControlLabel
value={course[field]}
label={label}
labelPlacement="end"
control={
<Switch
checked={course[field]}
color={'primary'}
onClick={updateField(course.id, field, true)}
{editableFields.map(({field, label, type = 'text', disabled = false}) => {
switch (field) {
case 'theia_default_image':
return (
<Grid item xs={12} key={field}>
<Autocomplete
fullWidth
value={course[field]}
onChange={(_, v) => updateField(course.id, field, false, false, true)(v)}
options={images}
getOptionLabel={(option) => option.label}
renderInput={(params) => <TextField {...params} label={label} variant="outlined" />}
/>
</Grid>
);
}
switch (type) {
case 'text':
return (
<Grid item xs={12} key={field}>
<TextField
fullWidth
disabled={disabled || _disabled}
variant={'outlined'}
label={label}
value={course[field]}
onChange={updateField(course.id, field)}
/>
</Grid>
);
case 'boolean':
return (
<Grid item xs={12} key={field}>
<FormControlLabel
value={course[field]}
label={label}
labelPlacement="end"
control={
<Switch
checked={course[field]}
color={'primary'}
onClick={updateField(course.id, field, true)}
/>
}
/>
}
/>}
</Grid>
))}
</Grid>
);
}
return null;
})}
</Grid>
</CardContent>
{!_disabled ? (
Expand Down
6 changes: 2 additions & 4 deletions old-web/src/Pages/Admin/Course.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ export default function Course() {
}, [reset]);

const updateField = (id, field, toggle = false, datetime = false, json = false) => (e) => {
if (!e) {
return;
}

if (course.id === id) {
if (toggle) {
course[field] = !course[field];
} else if (datetime) {
course[field] = format(e, 'yyyy-MM-dd HH:mm:ss');
} else if (json) {
course[field] = e;
} else {
course[field] = e.target.value.toString();
}
Expand Down

0 comments on commit 54ac8f6

Please sign in to comment.