Commit 69971f4c authored by Cyril Matthey-Doret's avatar Cyril Matthey-Doret
Browse files

improve readme, add license

parent ee4b01f7
Pipeline #329399 passed with stage
in 11 seconds
Copyright 2022 Cyril Matthey-Doret
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# Advanced teaching automation :robot: :mortar_board:
Leverage Renku and Gitlab to automate class management.
Leverage Renku and Gitlab to automate classroom management.
## Target audience
......@@ -16,9 +16,11 @@ These scripts can be used as-provided, but are also meant to be extended and ada
The repository is setup as a local python package.
You can install it with: `pip install -e .`
Most script below require a Gitlab API token. By default, you will be prompted for your token upon running the scripts, but you can also provide it as a plain text file with the `--token` option.
## Content
The following tasks are covered:
The following tasks are automated:
* [Inviting students to the class group](#inviting-students-to-the-class-group)
* [Creating student groups](#creating-student-groups)
......@@ -26,32 +28,55 @@ The following tasks are covered:
* [Cloning all forks of a project](#cloning-all-forks-of-a-project)
* [Sending feedback](#sending-feedback)
---
### Inviting students to the class group
Creating a single group for the class allows to have a centralizd place to store all materials, students repositories and exercises. In some cases, one may also want to make this group private.
Creating a single group for the class provides a centralizd place to store all materials, student repositories and exercises. In some cases, one may also want to make this group private.
The manual process of inviting the whole class to the group can be automated using Gitlab's invitation API. Given a list of student email addresses (e.g. exported from moodle) and the URL to a an existing gitlab group, we provide a script to automatically send an invitation email to each student.
<details>
<summary> <b>Read more...</b> </summary>
The manual process of inviting the whole class to the group can be automated using Gitlab's invitation API. Given a list of student email addresses (e.g. exported from moodle) and the URL to a gitlab group, we provide a script to automatically send an invitation email to each student.
script: [teach\_utils/invite\_students.py](teach_utils/invite_students.py)
script: [teach_utils/invite_students.py](teach_utils/invite_students.py)
**usage**:
usage: `python invite_students.py emails.txt https://gitlab-instance.com/group-name`
Invite students to `class-group`:
`python invite_students.py emails.txt https://gitlab-instance.com/class-group`
</details>
---
### Creating student groups
When students work on graded assignments, they will usually have to work in private groups. The groups must be accessible to the teachers in order to grade the assignments.
When students work on graded assignments, they usually have to work in private groups. The groups must be accessible to the teachers in order to grade the assignments.
One way to achieve this is to have the teacher create all private student groups (The teacher will therefore own the groups) and then invite students to their respective groups. We provide a script to automate the group creation and invitation process. It creates one private group for each student based on their information in a CSV file exported from Moodle.
One way to achieve this is to have the teacher create all private student groups (they will therefore be the group owner) and then invite students to their respective groups. We provide a script to automate the group creation and invitation process. It creates student groups based on their information in a CSV file exported from Moodle.
<details>
<summary> <b>Read more...</b> </summary>
script: [teach_utils/moodle_to_student_groups.py](teach_utils/moodle_to_student_groups.py)
script: [teach\_utils/moodle\_to\_student\_groups.py](teach_utils/moodle_to_student_groups.py)
usage: `python moodle_to_student_groups.py] students_moodle.csv`
**usage**:
Create student (sub)groups inside `class-group`:
`python moodle_to_student_groups.py students_moodle.csv https://gitlab-instance.com/class-group`
</details>
---
### Gathering all forks of a project
Homework assignment can be provided by the teachers in the form of a repository containing instructions and depencencies required. Students can then fork this project into their own private groups (e.g. created with the script above).
Homework assignment can be provided by the teachers in the form of a repository containing instructions and depencencies. Students can then fork this project into their own private groups (e.g. created with the aforementioned script).
The teacher can then use the Gitlab API to keep track of all student's forks. We provide a script to gather metadata about all student-group forks of a given project, with the option to retrieve the last commit before a deadline. That script will output the metadata as a JSON structure containing the following fields for each fork:
The teacher can then use the Gitlab API to keep track of student forks. We provide a script to gather metadata about all student-group forks of a given project, with the option to retrieve the last commit before a deadline. That script will output the metadata as a JSON structure containing the following fields for each fork:
<details>
<summary> <b>Read more...</b> </summary>
* `id`: The gitlab project ID of the fork
* `url`: The https URL to reach the fork
......@@ -63,40 +88,115 @@ The teacher can then use the Gitlab API to keep track of all student's forks. We
* `commit`: The hash of the last commit before the deadline, used for grading the assignment.
* `autostart_url`: A URL to directly start a Renku session at the last commit before the deadline.
script: [teach_utils/collect_forks.py](teach_utils/collect_forks.py)
> Note: the script assumes Gitlab commit timestamps and the deadline are in the UTC timezone.
script: [teach\_utils/collect\_forks.py](teach_utils/collect_forks.py)
usage:
**usage**:
Collect all group-owned forks of upstream-project, and write the JSON list to `forks.json`. Commits and autostart URLs will point to the last commit before 23h59 on March 15, 2022:
```sh
python collect_forks.py \
--deadline "YYYY-MM-DDThh-mm-ss" \
--deadline "2022-03-15T23:59" \
https://gitlab-instance.com/namespace/upstream-project \
> forks.json
```
**sample output:***
```
[
{
"id": 379,
"url": "https://renkulab.io/gitlab/demo-group/assignment.git",
"visibility": "private",
"group": "demo-group",
"members": [
{
"username": "studenta",
"name": "Student A",
"email": student.a@email.here
}
],
"commit": "0175894cf61fe820fdb84e5c52fc7ee0259a3c71",
"autostart_url": "https://renkulab.io/projects/demo-group/assignment/sessions/new?autostart=1&commit=0175894cf61fe820fdb84e5c52fc7ee0259a3c71&branch=master"
},
...
]
```
</details>
---
### Cloning all forks of a project
In some cases, the student projects may have to be cloned locally by the teacher. We provide a shell script to automate this process. This script will read the JSON output from `collect_forks.py` and clone all the forks at the deadline commit into a target directory.
In some cases, the student projects may have to be cloned locally by the teacher. We provide a shell script to automate this process. It reads the JSON output from `collect_forks.py` and clones all the forks at the deadline commit into a target directory.
<details>
<summary> <b>Read more...</b> </summary>
script: [teach\_utils/clone\_forks\_from\_json.sh](teach_utils/clone_forks_from_json.sh)
script: [teach_utils/clone_forks_from_json.sh](teach_utils/clone_forks_from_json.sh)
**usage**:
usage: `python clone_forks_from_json.sh forks.json clone_dir`
Clone all forks listed in `forks.json` into `clone_dir`:
`./clone_forks_from_json.sh forks.json clone_dir`
Or reading directly from stdin:
```
python ./collect_forks.py https://gitlab-instance.com/namespace/upstream-project \
| ./clone_forks_from_json.sh clone_dir
```
</details>
---
### Sending feedback
After grading student assignment, the teacher may want to send student groups their grades, as well as positive or critical comments on their solution. This can be automated as well, using the Gitlab issues API. We provide a script to read a csv file containing grades and comments for each student group and open issues in the corresponding repositories automatically.
After grading student assignments, the teacher may want to send student groups their grades, as well as positive or critical comments on their solution. This can be automated as well, using the Gitlab issues API. We provide a script to read a csv file containing grades and comments for each student group and open issues in the corresponding repositories automatically.
<details>
<summary> <b>Read more...</b> </summary>
script: [teach\_utils/send\_feedback.py](teach_utils/send_feedback.py)
**usage**:
script: [teach_utils/send_feedback.py](teach_utils/send_feedback.py)
By default, this script opens issues in all forks listed in `forks.json`. For each fork, fields {grades}, {comments} in the issue description are filled using column values in a CSV file provided with `--feedback`. The issue description uses a default template defined in the script:
usage: `python ./send_feedback.py forks.json grades.csv`
`python ./send_feedback.py --feedback grades.csv forks.json`
By default, this script will read grades and comments from `grades.csv` and open an issue for each repository in forks.json. The issue description uses a default template with grades and comments inserted in it. Forks can also be read from stdin, and users can specify a default template. One could also use this script to warn all groups that their fork is public for example:
where grades.csv is:
```
id,grade,comments
379,6,great use of list-comprehension!
424,5.5,Missing imports
```
A custom template can also be provided, and fields will be replaced by values from columns with corresponding names in `--feedback`. One could also use this script to warn all groups that their fork is public for example:
```
echo ":warning: Your fork is public, please make it private!" > visibility.md
./collect_forks https://renkulab.io/gitlab/class/homework \
./collect_forks.py https://renkulab.io/gitlab/class/homework \
| jq '.[] | select(.visibility == "public")' \
./send_feedback.py --template visibility.md grades.csv
| ./send_feedback.py --template visibility.md
```
> Note: `jq` is a tool to process JSON data. It can be used to reformat or filter the output of `collect_forks.py` in many ways. A number of useful one liners are [shown here](./oneliners.md).
</details>
## Contributing
Contributions related to common use-cases, or improvements are welcome.
Testing is done with `pytest --doctest-modules` and can be run with `make test`.
## License
These scripts are provided under the [MIT license](./LICENSE).
\ No newline at end of file
......@@ -42,9 +42,9 @@ def fill_template(content: Dict[str, str], template: str) -> str:
>>> content = {'name': 'Bob'}
>>> fill_template(content, 'I am {name}')
'I am Bob'
>>> fill_template(content | {'age': 3}, 'I am {age} yo')
'I am 3 yo'
>>> fill_template(content, 'I am {age} yo.') # doctest: +IGNORE_EXCEPTION_DETAIL
>>> fill_template(content | {'age': 13}, 'I am {age}')
'I am 13'
>>> fill_template(content, 'I am {age}') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError: Fields missing from content: ['age']
......@@ -76,7 +76,7 @@ def fill_template(content: Dict[str, str], template: str) -> str:
@click.option(
"--feedback",
type=click.Path(exists=True),
help="CSV file with column 'id', and additional columns matchine fields in the template"
help="CSV file with column 'id', and additional columns matching fields in the template"
"The id column should match a gitlab project ID from the JSON input.",
default=None,
)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment