Skip navigation
All Places > Blackboard Developer Community > Blog > Authors ahulme

Blackboard Developer Community

5 Posts authored by: ahulme

Challenge:  Batch assigning users to Admin roles within Nodes.  When you take advantage of the Community systems Learning Context Hierarchy it can be time consuming to assign many users roles is each node.  This was a challenge for Queen Margret University and  Joe Currie talked to us at TLC in Manchester on Dev Day to see if we had a better way.

 

Solution:  I have created a very simple B2 to help with this task.   This B2 adds a new system Admin tool.

Batch Node Admin Roles 

 

 

 

 

 

 

Copy and paste Pipe separated List of User and Roles with the Node Batch UID. This currently only supports one role per user per Node. Use a carriage return for each new user record. The format is user name|Node Batch UID|role name e.g. fflintstone|68555d7b-6445-43b0-a5e1-dd794d0ce2f1|User Administrator

 

Example:

 

AH1|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

AH2|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

AH3|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

AH4|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

AH5|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

AH6|3875808e-9a59-4ae7-8782-be9961a40b5f|User Administrator

 

Technical Details:

 

The B2 is based from the fabulous temple B23 form the All the Ducks Team.  GitHub - AllTheDucks/atd-b2-stub: Building Block template project

 

The Node manager does all the hard work for you.  Load in the Node ID a user list and a role list.

 

NodeManagerImpl n = new NodeManagerImpl();
n.addNodeAdmins(getnodeinfo.getNodeId(), userlist, rolelist);

 

Limitations:  Only one role can be assigned via this tool at the moment per user per node.  If a user already has a role this will be removed and updated with the new role.

 

Going Forward: I am happy to share the B2 or the code with anyone who wants to test or modify this project.  There is plenty of scope for improvements and I hope this will be made redundant in future as our REST API's get more mature and feature rich.

 

I had a support case recently to check the Custom Parameters being send from Learn via LTI.  My normal go to is http://ltiapps.net as this works great for testing LTI both as a consumer and provider.

 

In my case I wanted to test using SSL and have a go with NodeJS.

 

Borrowing some of the code from the Signup List B2-to-REST Migration it was simple to create my first LTI tool with NodeJS just to return the values passed in the LTI Body. The project is attached here.

 

 

app.js

 

/*jshint sub:true*/
var https = require('http');
var fs = require('fs');
var express = require('express');
var app = express();
var lti = require('ims-lti');
var _ = require('lodash');
var bodyParser  = require('body-parser');
//I got the key I got the secrete #Urban Cookie Collective

var ltiKey = "mykeyagain";
var ltiSecret = "mysagain";

app.engine('pug', require('pug').__express);

app.use(express.bodyParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.set('view engine', 'pug');

//Setup a POST endpoint to take anything going to /launch_lti
app.post('/launch_lti', function(req, res, next){

  req.body = _.omit(req.body, '__proto__');
      if (req.body['oauth_consumer_key']===ltiKey){
          var provider = new lti.Provider(ltiKey, ltiSecret);
         //Check is the Oauth  is valid using the LTI plugin for NodeJS.
              provider.valid_request(req, function (err, isValid){
                  if (err) {
                  console.log('Error in LTI Launch:' + err);
                  res.status(403).send(err);
                  
                  }
                  else {
                  if (!isValid) {
                    console.log('\nError: Invalid LTI launch.');
                    res.status(500).send({ error: "Invalid LTI launch" });
                     } 
                  else {
                      //User is Auth so pass back when ever we need. in this case we use pug to render the values to screen
                      res.render('start', { title: 'LTI SETTINGS', CourseID: 'CourseID: '+req.body['context_id'], userID: 'UserID: '+req.body['user_id'], UserRole: 'Course Role: '+req.body['roles'], FulllogTitle: 'Full Log: ', Fulllog: JSON.stringify(req.body) });
            }}
       });
    }
  else {
      console.log('LTI KEY NOT MATCHED:');
      res.status(403).send({ error: "LTI KEY NOT MATCHED" });      
  }

});

//Setup the http server, When delyed locally this will run on port 5000, when deployed on Heroku it will assign a port and add SSL
var server = https.createServer(app).listen(process.env.PORT || 5000, function(){
  console.log("https server started");
});

 

 

I then deployed this to heroku so It would add SSL and is available for anyone to test.

 

 

Tool URL: https://porttestsupport.herokuapp.com/launch_lti

Key: mykeyagain

Secrete: mysagain

 

Add this to Learn as a course tool and when clicked it will give you the full Parameter list sent to the LTI tool.  This is the output from a Test Student.

 

Now we have a working Building Block lets add some more information to the course tool as a form.

 

CourseToolConfig.java

 

//Create a LIST filled with Group Objects using the Course ID.

 

  private static List<Group> GroupObjects(String course_id) throws PersistenceException{
        GroupDbLoader groupDbLoader = GroupDbLoader.Default.getInstance();
        List<Group> groups = groupDbLoader.loadAvailableByCourseId((Id.generateId(new DataType(blackboard.data.course.Course.class), course_id)));

        List<Group> G1 = new LinkedList<>();

            for (Group group : groups){
                
                //Get group titles. Add to Linked list g1.
                G1.add(group);
                System.out.println("Group Size"+group.getGroupMemberships().size());
    
                
            }
            
            return G1;
        
    }

 

 

Now we need to add some Getter's and Setter's for the List we will pass to the JSP page.

 

 

private List<Group> groupTitles; 
public List<Group> getGroupTitles() {return groupTitles; }
public void setGroupTitles(List<Group> groupTitles) { this.groupTitles = groupTitles; }

 

 

In our Default Handler   @DefaultHandler   public Resolution displayPage() we can populate this list

 

groupTitles = GroupObjects(course_id);

 

To display this in the JSP we use the actionBean.groupTitles List and then can call out the getTitle and getGroupMemberships().size() from the Group Object in the List

 

<bbNG:form action="coursetool" method="post">

        <bbNG:dataCollection>

            <bbNG:step title="Groups">

         

                        <bbNG:inventoryList description="Group Names"  className="Group" collection="${actionBean.groupTitles}" objectVar="title" emptyMsg="No data to display">

                        <bbNG:listElement  name="Group" label="Group" isRowHeader="true">${title.getTitle()}</bbNG:listElement>

                        <bbNG:listElement  name="Group Enrollments" label="Group Enrollments"  isRowHeader="false">${title.getGroupMemberships().size()}</bbNG:listElement>

                        <bbNG:listCheckboxElement  title="Title" name="${title.getTitle()}" id="ID" value="Y"></bbNG:listCheckboxElement>

                </bbNG:inventoryList>

 

                        <input type="hidden" name="course_id" value="${actionBean.course_id}" />

                     

                     

                 

            <bbNG:stepInstructions text="    ${message}  <BR>

               Course Title =  ${actionBean.course_title} <BR>

               Course Pk1 =  ${actionBean.course_id}  <BR>

               User Name  = ${actionBean.username}

               "/>

            </bbNG:step>

        <bbNG:stepSubmit  cancelUrl="Cancle" />

        </bbNG:dataCollection>

    </bbNG:form>

In part one we build a basic course tool.  In this part we will use the config utility to set dome value for our B2.  These can be used for things like Global user/pass.

 

The B2 stub uses ATD's Configuration Utilities Library to provide configuration services to the building block. This building block handles the saving, loading and caching of a configuration bean.

 

 

The Configuration.java contain the list of Getters and Setters for this page. More can be added of modified.

 

package com.devday.config;

import net.sourceforge.stripes.validation.Validate;

/**
 * Created by wiley on 19/11/14.
 */
public class Configuration {

    private String settingOne;
    private int settingTwo;

    public String getSettingOne() {
        return settingOne;
    }

    public void setSettingOne(String settingOne) {
        this.settingOne = settingOne;
    }

    public int getSettingTwo() {
        return settingTwo;
    }

    public void setSettingTwo(int settingTwo) {
        this.settingTwo = settingTwo;
    }
}

 

 

Stripes config.action

 

@LoginRequired
@EntitlementRestrictions(entitlements = "bbs.yourb2name.admin.MODIFY", errorPage = "/error.jsp")
@UrlBinding("/config")
public class ConfigAction implements ActionBean {

    private BlackboardActionBeanContext context;

    @ValidateNestedProperties({@Validate(field = "settingOne", required = true),
            @Validate(field = "settingTwo", required = true, minvalue = 0, maxvalue = 100)})
    private Configuration config;

    @SpringBean
    private ConfigurationService<Configuration> configService;

    @Before(stages = LifecycleStage.BindingAndValidation)
    public void loadConfiguration() {
        config = configService.loadConfiguration();
    }


    @DefaultHandler
    @DontValidate
    public Resolution displayConfigPage() {
        return new ForwardResolution("/WEB-INF/jsp/config.jsp");
    }

    public Resolution saveConfiguration() {
        configService.persistConfiguration(config);
        return new RedirectResolution(PlugInUtil.getPlugInManagerURL(), false);
    }

    @Override
    public ActionBeanContext getContext() {
        return context;
    }

    @Override
    public void setContext(ActionBeanContext context) {
        this.context = (BlackboardActionBeanContext)context;
    }

    public Configuration getConfig() {
        return config;
    }

    public void setConfig(Configuration config) {
        this.config = config;
    }
}

 

 

Note the Form Validation, if you make a change to the methods for example making the int a String you will need to update this validation to match the type.

 

@ValidateNestedProperties({@Validate(field = "settingOne", required = true),     @Validate(field = "settingTwo", required = true, minvalue = 0, maxvalue = 100)})

 

Default Values:

 

If you want to pre-populate these configs with default values then edit the defultconfig.xml under src/main/resources

 

<com.devday.config.Configuration>
  <settingOne>A default string value</settingOne>
  <settingTwo>42</settingTwo>
</com.devday.config.Configuration>

 

Config JSP.

 

This uses a simple Form to collect the data and pass via the Stripes Action

 

 

 

<stripes:form beanclass="com.devday.stripes.ConfigAction">

        <stripes:param name="saveConfiguration"/>

 

        <bbNG:dataCollection>

            <bbNG:step title="${toolSettingsStepTitle}" instructions="${toolSettingsStepInstructions}">

                <bbNG:dataElement isRequired="true" label="${settingOneLabel}">

                    <stripes:text name="config.settingOne"></stripes:text>

                    <stripes:errors field="config.settingOne"></stripes:errors>

                </bbNG:dataElement>

                <bbNG:dataElement isRequired="true" label="${settingTwoLabel}">

                    <stripes:text name="config.settingTwo"></stripes:text>

                    <stripes:errors field="config.settingTwo"></stripes:errors>

                </bbNG:dataElement>

            </bbNG:step>

            <bbNG:stepSubmit></bbNG:stepSubmit>

        </bbNG:dataCollection>

    </stripes:form>

For those of you joining the DevCon in Milan we will be running a Get you started session for Building Blocks using the fantastic Building Block template from the 'All the Ducks' Team.  This uses Stripes as its framework and Gradle to Build the project.

 

This will be a Multi-part series of Blog posts designed to get your up and running with your first B2.

 

If you would like to join the session then it will be helpful to run though this guide before hand and install Eclipse and Git Bash.

 

We will cover in the session:

 

  1. Getting you started with Eclipse
  2. Language pack Bundles
  3. API calls
  4. Data Handling

 

 

Install Eclipse:

 

Download and install Eclipse from Eclipse - The Eclipse Foundation open source community website.

 

Download Git bash for Windows Git - Downloading Package

 

Getting Started.

 

Launch git bash and CD to your Work space. I am using

 

ahulme@AHULMEP50 MINGW64 ~

$ cd workspace/devday/

 

Clone the template to your local Workspace.  Note the First part of the directory name shroud be you Vendor ID. In my case I am using bbs  (Use lowercase chars) and then the name of the B2.  This example uses bbs-yourb2name

 

 

ahulme@AHULMEP50 MINGW64 ~/workspace/devday

$ git clone https://github.com/AllTheDucks/atd-b2-stub.git bbs-yourb2name

Cloning into 'bbs-yourb2name'...

remote: Counting objects: 712, done.

remote: Total 712 (delta 0), reused 0 (delta 0), pack-reused 712 eceiving objects:  99% (705/712)

Receiving objects: 100% (712/712), 206.33 KiB | 0 bytes/s, done.

Resolving deltas: 100% (323/323), done.

Checking connectivity... done.

 

Change into the project directory

 

ahulme@AHULMEP50 MINGW64 ~/workspace/devday

$ cd bbs-yourb2name/

 

Run the initB2 task and answer all the questions.  Make sure you set the Vendor ID to match the one used above and the B2 handle as the same name as above.

 

ahulme@AHULMEP50 MINGW64 ~/workspace/devday/bbs-yourb2name (master)

$ ./gradlew initB2

:initB2

Defaults found for generating building block

> Building 0% > :initB2

What is your Vendor Id (e.g. usq, unsw, qut, swin, etc)? (No spaces) [BBS] bbs

What is your Vendor Name (e.g. University of Antarctica)? [Blackboard Support]

What is your Vendor Website (e.g. http://www.myu.edu.au/)? (No spaces) [http://community.blackboard.com/]

What is your B2 Handle (e.g. jshack, autosignon, etc)? (No spaces)  yourb2name

What is your B2 Name (e.g. Student View)? Dev Day2016         *Note I created this in January and forgot we moved on a year, its just to cold in Jan to think....

What is the base java package of your project? (e.g. au.edu.uni.myproject) ? (No spaces) com.devday

Do you want to use the course event listener? (Y/N) Y

Do you want to use the Schema.xml? (Y/N) Y

Do you want a system tool? (Y/N) Y

Do you want a course tool? (Y/N) Y

Initializing Building Block bbs-yourb2name

Initialized empty Git repository in C:/Users/ahulme/workspace/devday/bbs-yourb2name/.git/

 

BUILD SUCCESSFUL

 

Total time: 1 mins 59.188 secs

 

Import the Project into Eclipse.

 

File > Import > Gradle Project

 

Browse to the Directory your created and Select Build model.  Check the Project and finish.  This has now imported this into your work-space.

 

 

 

This is the project as it looks in Eclipse.    There are 3 stripes action classes in src > com.devday.stripes .  These control the actions for the Course Tool, System Tool and Config tool.  The JSP files for these tools can be found in src > main > webapp > WEB-INF > jsp.  The language bundles are in  src > main > webapp > WEB-INF > bundles

 

 

Build the B2.

 

Without making any changes we can Build this B2 and Deploy it to Blackboard.  This will create a course tool, a system tool, Some Database Schema objects and a Config tool for the B2.

 

Right click gradlew > run as > Gradle STS Build

Set the task to build > apply and then run

 

Console Output:

 

[sts] -----------------------------------------------------

[sts] Starting Gradle build for the following tasks:

[sts]      build

[sts] -----------------------------------------------------

:compileJavawarning: [options] bootstrap class path not set in conjunction with -source 1.7

1 warning

 

:processResources

:classes

:war

:assemble

:compileTestJava UP-TO-DATE

:processTestResources UP-TO-DATE

:testClasses UP-TO-DATE

:test UP-TO-DATE

:check UP-TO-DATE

:build

 

BUILD SUCCESSFUL

 

Total time: 1.584 secs

[sts] -----------------------------------------------------

[sts] Build finished succesfully!

[sts] Time taken: 0 min, 1 sec

[sts] -----------------------------------------------------

 

Deploy To Blackboard.

 

System admin GUI > Building Blocks > installed tools > Upload Building block

 

If you see this error in Red you will need to enable B2's to add custom objects. Building Blocks > Global Settings

 

Error: Could not install plugin.name. There are associated database changes and the global setting has been set to prevent any Building Block from creating custom database objects.

 

 

 

The war file can be found in the build\libs\ directory of the project space.

 

There will be a new tool now in the System admin panel highlighted below.

 

 

Click on this new link and you will see the tool.  As we have not configured this tool to do anything as yet a simple message will show to confirm it is working.   Note the URL used for this tool is https://Yoururl.com/webapps/bbs-yourb2name-BBLEARN/systemtool

 

 

The course tool is set by default to be unavailable courses so you can either set this globally or set on a per course bases for testing.  In a course  Customization > Tool Availability  > find the tool called Dev Day2016 and enable it.  You can then find this tool under tools and select it.

 

 

 

 

The B2 also has a config page for setting Global Configurations.  This can be access though the settings page and has buy default a String and an Integer value. We can change these and add more if required.

 

 

 

 

 

Course Tool Action.

 

This controls the Behavior when the course tool link is selected.  The URL https://finn.pd.local/webapps/bbs-yourb2name-BBLEARN/systemtool is mapped to this CourseToolActoin.java and the default action is to rerun the JSP /WEB-INF/jsp/course-tool.jsp

 

package com.devday.stripes;

import com.alltheducks.bb.stripes.BlackboardActionBeanContext;
import com.alltheducks.bb.stripes.EntitlementRestrictions;
import com.alltheducks.bb.stripes.LoginRequired;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.integration.spring.SpringBean;

@LoginRequired
@EntitlementRestrictions(entitlements = "bbs.yourb2name.course.VIEW", errorPage = "/error.jsp")
@UrlBinding("/coursetool")
public class CourseToolAction implements ActionBean {

    private BlackboardActionBeanContext context;

    @DefaultHandler
    public Resolution displayPage() {
        return new ForwardResolution("/WEB-INF/jsp/course-tool.jsp");
    }

    @Override
    public ActionBeanContext getContext() {
        return context;
    }

    @Override
    public void setContext(ActionBeanContext context) {
        this.context = (BlackboardActionBeanContext)context;
    }
}

 

 

A simple course Tool Project:

 

lets use our basic course tool to display some information about the course.  The FULL URL called for the tool link is https://finn.pd.local/webapps/bbs-yourb2name-BBLEARN/coursetool?course_id=_36_1  and this contain the course Pk1.  We can use this to get the course Pk1 and use the context to get the Course Title

 

Add the following to the CourseToolActoin.java on line 32 to setup our Getters and Setters.

 

private String course_title;

public String getCourse_title() {return course_title; }

public void setCourse_title(String course_title) { this.course_title = course_title; }

   

private String course_id;

public String getCourse_id() {return course_id; }

public void setCourse_id(String course_id) { this.course_id = course_id; }

 

The course Pk1 will be automatically mapped to the action bean course_id as this is provided in the URL.  We can set the Course Title using the context Stripes Action Bean.

 

package com.devday.stripes;

import com.alltheducks.bb.stripes.BlackboardActionBeanContext;
import com.alltheducks.bb.stripes.EntitlementRestrictions;
import com.alltheducks.bb.stripes.LoginRequired;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.integration.spring.SpringBean;

@LoginRequired
@EntitlementRestrictions(entitlements = "bbs.yourb2name.course.VIEW", errorPage = "/error.jsp")
@UrlBinding("/coursetool")
public class CourseToolAction implements ActionBean {

    private BlackboardActionBeanContext context;

    @DefaultHandler
    public Resolution displayPage() {
        
        //Set the actionbean.course_id to the  Course Title from the context
        course_title = context.getCourse().getTitle();
        
        
        return new ForwardResolution("/WEB-INF/jsp/course-tool.jsp");
    }

    @Override
    public ActionBeanContext getContext() {
        return context;
    }

    @Override
    public void setContext(ActionBeanContext context) {
        this.context = (BlackboardActionBeanContext)context;
    }
    
    
    private String course_title; 
       public String getCourse_title() {return course_title; }
    public void setCourse_title(String course_title) { this.course_title = course_title; }
    
    private String course_id; 
       public String getCourse_id() {return course_id; }
    public void setCourse_id(String course_id) { this.course_id = course_id; }
    
}


 

in the course_tools.jsp add in the actionBeans to display the Course Title and Pk1