Adding tasks and gating content
In the previous section you learned about activities and added a quiz to the lab. In this section we will explore another type of activity called tasks.
Tasks are a way to verify the hands-on skills and knowledge of a user by having them perform actions within the sandbox environment, and then validating that the desired outcome of the actions is reached.
Tasks are made up of one or more conditions that need to be met before the task is considered completed. A condition tells the user what needs to be completed, and specifies lifecycle scripts that are executed for the condition.
For example, lets say we have a task where we want the user to write a certain value e.g. world
to a file in a certain location e.g. /tmp/hello
.
This task can be split up into a few steps or conditions:
the user needs to create a file at the given location
the user needs to write the given value to the created file
When writing the description of a condition, try to describe the end result you want instead of the steps to get there, because there are often multiple valid ways to get the same result. For the previous two conditions, you could write them as:
The file
/tmp/hello
should existThe file should contain the text
world
Lets start by creating a file tasks.hcl
to hold the task
resource and name it "helloworld".
Add two conditions to it and set their description
field.
Lifecycle hooks
Now that you have defined the task
and its conditions
, the next step is to specify what scripts need to be run in order to validate each of the conditions.
This is done by specifying lifecycle hooks on the conditions.
The lifecycle of a task and its conditions is split up into setup
, check
, solve
, cleanup
.
The setup
lifecycle scripts are run when the task and its conditions enter the unlocked
state.
This lifecycle hook can be used to do any setup that might be needed in order to prepare the sandbox for the user actions of this specific task and uts conditions.
The check
lifecycle scripts are run when the user clicks the "Check" button of an embedded task component in the instructions.
This lifecycle hook is used to validate if the desired outcome has been reached for each condition.
The solve
lifecycle scripts are run when the user clocks the "Solve for me" button of an embedded task component in the instructions.
This lifecycle hook is used to solve the task for the user and mimic the steps the user would have to do to reach the desired outcome.
If the actions of the user would not impact any following tasks, then the solve
lifecycle can be omitted to just skip the task altogether.
The cleanup
lifecycle scripts are run when the task has successfully been completed
or skipped
.
This lifecycle hook can be used to do any cleanup that might be needed in order to prepare the sandbox for upcoming user actions.
Each condition can have as many lifecycle hooks as needed, depending on the situation. For instance, you could split the scripts that you want to execute for a condition logically in order to keep them simple and reusable. Or if you wanted to execute multiple scripts, but each on a different target or with different settings.
Lets add a check
block to each of the conditions of the task, to execute scripts when the user clicks the "Check" button.
Scripts
When specifying a lifecycle hook, it needs to know which script to run for that hook.
You do this by providing a path to the script file in the script
field.
Lets create a script file scripts/file_exists.sh
and add the following contents:
Then tell the check
of the first condition "file_exists" to use the created file by setting the script
field to scripts/file_exists.sh
.
In order to give clear feedback to the user when the check script fails, you can specify a failure_message
.
This message will de displayed to the user on the task component for the given condition.
Add a failure message to the check of the "file_exists" condition and set the message to "The file /usr/share/nginx/html/index.html
does not exist".
Create another script file scripts/contents_match.sh
with the following contents:
Add it to the check
of the second condition file_contains
and set the failure_message
to "The file /usr/share/nginx/html/index.html
does not contain the text hello world
".
In order for the scripts to be executed in the correct place and with the correct settings, they need to be configured.
This configuration can be specified on the task
with a config
block, and any of the fields can be overridden in the condition
and lifecycle hook config
blocks.
This can be particularly useful when only a subset of lifecycle scripts requires non-default settings e.g. to set a longer timeout
duration for a script that runs longer than all the others.
In the case of the task you created, all scripts can be executed the same way.
This means that the config
can be specified once on the task
resource, and then used by each of the conditions and each of their lifecycle hooks.
The only configuration setting that needs to be specified is where to run the script, by setting the target
field of the config
block to a reference of the "webserver" container (resource.container.webserver
) you created earlier.
Embed the task in a page
Now that you have defined the task
and configured all its conditions and lifecycle hooks, lets use it in the instructions.
In pages.hcl
add a new page
resource and name it "task".
Add the resource.task.helloworld
task to the activities
map, like before when adding a quiz in the "Adding quizzes" section.
Then create a markdown file instructions/task.md
, with the following contents.
To embed the task component in the page, add the instruqt-task
component and pass it the id
that you mapped the resource.task.helloworld
task to e.g. "edit_html".
Inside of the instruqt-task
html tags, you can specify instructions for the user that they need to follow to complete the task.
Together with the descriptions of the conditions, this is what instructs the user to perform the desired actions.
Change the contents of instructions/task.md
so it contains an instruqt-task
with the basic actions to complete.
Then add the created file
to the page
resource.
Adding a terminal
terminal
You now have actions for the user to perform, but no way for that user to actually perform them.
To fix this, lets add a terminal
resource named "shell" to the lab, so the user can interact with the "webserver" container using a terminal tab. On tabs.hcl
, add:
Just like the service
resource you added in the "Configuring Sandboxes" section, a terminal
resource also has a target
field that specifies which resource to provide terminal access to.
In this case we want to provide access to the "webserver" container, so set the target
field to a reference e.g. resource.container.webserver
.
This is enough to have a working terminal on a container resource, but to make it nicer to interact with for the user you can add additional configuration.
Set the default shell
that the user will be presented to /bin/bash
, to allow for things like tab completion.
And to already put the user in the correct location to edit the html files of the webserver, lets set the working_directory
to /usr/share/nginx/html
.
Add the new page and terminal the Lab UI
In order for the user to interact with the "shell" terminal
you just created, we need to connect a tab to it.
In the main.hcl
file, add a tab named "terminal" to the "two_columns" layout.
Assign the tab to the "right" panel, and set the target
to a reference to the terminal
resource e.g. resource.terminal.shell
.
Finally, to show the page containing the task
, add a page
block named "task" to the "introduction" chapter
block.
Set the reference
to the page
resource you created earlier resource.page.task
.
Preview your changes
Like in the previous section, you can validate your changes using instruqt lab validate
, and then git add
, git commit
, and git push
the changes to GitHub.
You can then go to the "Labs" section, verify the latest commit on GitHub matches the status of your lab, and start the lab.
Your lab should now have an additional page in the "introduction" chapter.
When you are going through the lab, you should notice that you are now not able to navigate past the page with the quiz as the next page is still locked. This is because the quiz needs to be successfully completed in order for the next page, and any of the activities embedded in that page, to be unlocked.
To get to the page with the task, solve the quiz and the "Next Page" button should become active.
The third page should display the task component and show the conditions you have defined. Try interacting with the task to see how it behaves when you submit the wrong answer, a partially wrong answer, and when you complete it.
Once you are done exploring your lab, click the "Stop" button at the top right of the Lab UI to shut down your lab environment and go back to the "Labs" list.
Last updated