Skip navigation
All Places > Blackboard Developer Community > Blog
ileana

Bb Open Content Features Survey

Posted by ileana Mar 15, 2019

As a reminder, effective June 30, 2019, Blackboard will transition Blackboard Open Content to maintenance support mode. Effective December 31, 2019 Blackboard will end the availability of Blackboard Open Content for all Blackboard clients.

 

As noted in the previous support bulletin, most of the content creation functionality included in Open Content can be achieved using the native functionality of the LMS. For the content sharing and updating functionality currently not available in the LMS, Blackboard is researching potential options to partner with third party vendors to address the functionality gaps. 

 

We would like your feedback in determining the features that are most important to our users. Please complete this survey to rank the functionalities you believe are most important.

 

Thank you for your feedback!

 

If there are any additional questions please reach out to Brad Koch and Jeannine Richardson.

Let me start by saying how much I appreciate the Developer Virtual Machine and the work that Blackboard has put into optimizing the install and upgrade process of Learn.

 

Unfortunately, I cannot use the DVM directly on my machine because I already have Hyper-V in use. It would require bcdedit & a reboot to toggle hypervisors.

 

To get going "quickly," I used VirtualBox's tools to convert the .vmdk to a .vhd. Then I manually attached the hard disk to a custom VM in Hyper-V. It works perfectly with the exception of shutting down/rebooting. The VM has to be reset again to get the bootloader to fire. My DVM eventually needs to be rebooted after repeatedly deploying a building block.

 

Our production and test environments are inside Blackboard's managed hosting, so going into this I had 0 experience with the server-side operations of Learn.

 

Once my project was done, I started to get curious. Now that I'll need to test my building block regularly, how do I install a signed certificate so that testers' browsers stop complaining?

  • The official documentation briefly goes over installing a signed certificate on a single-server install, but it leaves out the fact that the keystore password is now encrypted.

The DVM available was out of date. How do I upgrade it?

  • The upgrade process requires an installer.properties that was not left behind by the creator of the DVM. I found a blog post Upgrade Your DVM that includes everything you need to upgrade to the latest Learn version.

 

Ultimately the DVM is designed to be a throw-away install, but I need it to last longer and be a tad more secure. So I've documented the install process from scratch.

 

You will need:

 

RHEL 7.5 is a supported operating system, and I am familiar with it, so I am using CentOS.

 

You will also need a way to transfer files to the server. There are several ways including using scp, wget from a [local] server, mounting a share, or configuring samba.

 

With the hard drive capacity set to 20 GB, it will be 52% full after Learn installation. If you delete the installer.zip, it will be 43% full. If you delete the installer directory, it will be 27% full.

 

(Scenario A) Hyper-V Virtual Machine Setup

 

I use Hyper-V Manager on Windows 10 Pro.

 

Manually create a new Virtual Machine

  • under Specify Generation: select Generation 2
  • under Assign Memory: set 4096 MB, DISABLE the option "Use Dynamic Memory for this virtual machine"
  • under Configure Networking: select external switch
  • under Connect Virtual Hard Disk: create a virtual hard disk with 20 GB
  • under Installation Options: select Install an operating system from a bootable image file, and select .iso

 

Edit the newly created VM Settings

  • under Security: DISABLE the option "Enable Secure Boot"
  • under Processor: increase the "Number of virtual processors" (at least 2)
  • under Network Adapter: verify settings are correct (I need to set a VLAN id in my environment)
  • under Checkpoints: DISABLE the option "Use automatic checkpoints" - unless you want them!

 

(Scenario B) vSphere 6.5 (ESXi) Virtual Machine Setup

 

Manually create a new Virtual Machine

  • under Select a guest OS: select "Linux" family, select "CentOS 7 (64-bit)" version
  • under Customize hardware:
    • CPU: 2, cores per socket
    • Memory: 4 GB
    • New Hard disk: 20 GB
    • New Network: as needed
    • New CD/DVD Drive:
      • if you uploaded the iso to a datastore, select Datastore ISO file and ENABLE the option "Connect At Power On"
      • otherwise, select Client Device - you must manually connect the iso from the remote console

 

Starting the server build

 

Start the VM

  • The iso's bootloader should fire. You might have to play with the boot order.
  • Select Install CentOS 7

 

CentOS installer

  • under Network & Host Name:
    • click ON to enable Ethernet
    • set the Hostname if desired - make sure you click apply
  • if you are not using the Minimal installer iso:
    • under Software Selection: select Minimal Install

 

During install

  • under Root Password:
    • set Password as needed
  • under User Creation
    • set "Full name" to bbuser
    • set "User name" to bbuser
    • ENABLE the option "Make this user administrator"
    • set Password as needed

 

Reboot

 

Post OS install

 

If you need to manually set a static IP, using the console, login as bbuser

$ sudo nmtui

  • Edit a connection
    • set IPv4 - manual (did not test with IPv6 - ignore)
  • Activate a connection
    • toggle state for IP changes to take affect immediately

 

Login as bbuser using SSH

 

(optional) configure the wheel group to not require a password for sudo

$ echo '%wheel ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/wheel

 

Update to the latest packages

$ sudo yum update -y

 

Install the needed packages

$ sudo yum install open-vm-tools wget unzip postgresql-server postgresql-contrib policycoreutils-python

 

Transfer the latest Oracle JDK 8 for Linux x64 rpm to server (can be deleted later to reclaim space)

 

Install the jdk8 rpm

$ sudo yum localinstall jdk-8uXXXXX-linux-x64.rpm

 

Add a missing shared library for Java (the rpm does not include the link).

  • You can confirm this by running $ ldd `which java`

$ echo '/usr/java/latest/jre/lib/amd64/jli' | sudo tee /etc/ld.so.conf.d/java.conf; sudo ldconfig

 

Add JAVA_HOME (not required, but recommended) and PGDATA (required, location of DB) to default environment

$ echo -e '#!/bin/bash\nexport JAVA_HOME=/usr/java/latest\nexport PGDATA=/usr/local/bbdata' | sudo tee /etc/profile.d/bb-environment.sh

 

(optional) prepare for publickey auth

$ mkdir --mode=750 ~/.ssh; touch ~/.ssh/authorized_keys; chmod 640 ~/.ssh/authorized_keys

  • add your keys

 

Configure OpenSSH to only allow bbuser to login (no root)

$ echo -e 'AllowUsers bbuser' | sudo tee -a /etc/ssh/sshd_config

 

(option A) Redirect 8080/8443 to 80/443

$ sudo firewall-cmd --permanent --add-masquerade; sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080; sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443

 

(option B) Open 8080/8443

$ sudo firewall-cmd --permanent --add-port=8080/tcp; sudo firewall-cmd --permanent --add-port=8443/tcp

 

(optional) Allow postgresql remotely

$ sudo firewall-cmd --permanent --add-service=postgresql

 

(optional) Allow tomcat debugging remotely

$ sudo firewall-cmd --permanent --add-port=2222/tcp

 

(optional) Configure postfix if you need local mail configuration

$ sudo vi /etc/postfix/main.cf; sudo systemctl enable postfix

 

Set max number of open file descriptors

$ echo -e '@bbuser soft nofile 5000\n@bbuser hard nofile 5000' | sudo tee -a /etc/security/limits.conf

 

Overwrite default PGDATA environment variable for postgresql service

$ echo -e '.include /lib/systemd/system/postgresql.service\n[Service]\nEnvironment=PGDATA=/usr/local/bbdata' | sudo tee /etc/systemd/system/postgresql.service

 

Create PGDATA directory and assign ownership to postgres

$ sudo mkdir -p /usr/local/bbdata; sudo chown postgres:postgres /usr/local/bbdata

 

Relabel PGDATA's SELINUX context

$ sudo semanage fcontext -a -s system_u -t postgresql_db_t '/usr/local/bbdata(/.*)?'; sudo restorecon /usr/local/bbdata

 

(if ever needed) Disable SELINUX

  • temporarily:
    • $ sudo setenforce permissive
  • permanently, requires reboot
    • $ sudo sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

 

Reboot so that environment, firewall, and other settings take affect

$ sudo reboot

 

Login as bbuser using SSH

 

Initialize postgresql db; enable and start service

$ sudo postgresql-setup initdb; sudo systemctl enable postgresql; sudo systemctl start postgresql

 

Launch into psql as postgres

$ sudo -i -u postgres psql

 

From the postgresql shell paste in:

  • ALTER ROLE postgres WITH ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN" WITH LOGIN ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN_admin" WITH LOGIN ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN_cms" WITH LOGIN ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN_cms_doc" WITH LOGIN ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN_stats" WITH LOGIN ENCRYPTED PASSWORD 'postgres';
  • CREATE ROLE "BBLEARN_report" WITH LOGIN ENCRYPTED PASSWORD 'password';
  • \q

 

Configure postgresql authentication to require a password instead of using "current user"

$ sudo sed -i -e 's/ ident$/ md5/' -e 's/ peer$/ md5/' $PGDATA/pg_hba.conf; sudo systemctl restart postgresql

 

Add blackboard service to systemd; enable it

$ echo -e '[Unit]\nDescription=blackboard\nAfter=postgresql.service\n\n[Service]\nLimitNOFILE=5000\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/local/blackboard/tools/admin/ServiceController.sh services.start\nExecStop=/usr/local/blackboard/tools/admin/ServiceController.sh services.stop\n\n[Install]\nWantedBy=multi-user.target' | sudo tee /etc/systemd/system/blackboard.service; sudo systemctl enable blackboard

 

Create blackboard and bbinstaller directories and assign ownership to bbuser

$ sudo mkdir -p /usr/local/blackboard /usr/local/bbinstaller; sudo chown bbuser:bbuser /usr/local/blackboard /usr/local/bbinstaller

 

Create blackboard license file to server (plain-text in /usr/local/blackboard/config/license/blackboard-license.xml in a working DVM/test environment)

$ vi /usr/local/bbinstaller/blackboard-license.xml

 

Create installer.properties (included below)

$ vi /usr/local/bbinstaller/installer.properties

 

 

Install (and upgrade) Learn

 

Create a checkpoint/snapshot. It should be taken when the system is shutdown for maximum data consistency.

 

Set temporary variable with the new version. For example $ BBVERSION='3500_0_2'

$ BBVERSION='version_tag'

 

Create install directory (can be deleted later to reclaim space)

$ mkdir /usr/local/bbinstaller/$BBVERSION

 

Transfer learn-installer.zip file to server (can be deleted later to reclaim space)

 

Unzip installer.zip to install directory

$ unzip learn-installer-XXXXX.zip -d /usr/local/bbinstaller/$BBVERSION

 

Temporarily change to installer directory and then run the installer

$ pushd /usr/local/bbinstaller/$BBVERSION; ./installer.sh -c /usr/local/bbinstaller/installer.properties; popd

 

Run the PushConfigUpdates admin tool (also tries to fix any permission issues)

$ /usr/local/blackboard/tools/admin/PushConfigUpdates.sh --no-restart

 

Start the blackboard service

$ sudo systemctl restart blackboard

 

 

(optional) Install (and upgrade) Starting Block

 

Install and set AVAILABLE the Starting Block B2

$ /usr/local/blackboard/tools/admin/B2Manager.sh -i /usr/local/blackboard/system/autoinstall/internal.developer/allavailable/starting-block.war; /usr/local/blackboard/tools/admin/B2Manager.sh -s AVAILABLE bb-starting-block

 

Test to make sure it works

 

 

Post Install

 

After installing (and upgrading) review the output in the log files.

  • /usr/local/blackboard/logs/tomcat/stdout-stderr-*.log
  • /usr/local/blackboard/logs/tomcat/bb-access-log.*.txt
  • /usr/local/blackboard/logs/bb-services-log.txt

 

If you see ClassNotFoundException errors for com.blackboard.partners.* the Partner Cloud B2 is missing.

$ /usr/local/blackboard/tools/admin/B2Manager.sh -i /usr/local/blackboard/system/autoinstall/market.pro/allavailable/partner-cloud.war

 

 

(optional) Implement CA-signed Certificate for HTTPS

 

Run the ShowCleartextPasswords admin tool to output the current keystore password

$ /usr/local/blackboard/tools/admin/ShowCleartextPasswords.sh | grep appserver.keystore

 

Set temporary variable with the keystore file

$ BBKEYSTORE='/usr/local/blackboard/config/keystores/tomcat.keystore'

 

Rename current keystore so we have it in case of issues

$ mv $BBKEYSTORE $BBKEYSTORE.bak

 

Generate new private key for certificate

$ keytool -genkeypair -keystore $BBKEYSTORE -storetype jks -alias tomcat -keysize 2048 -keyalg RSA

  • When asked for "first and last name" input the CN of the certificate.

 

Output CSR

$ keytool -certreq -keystore $BBKEYSTORE -alias tomcat

 

Submit CSR to CA. The resulting signed certificate file should be a .p7b.

 

Transfer signedcert.p7b to server (it is plain text)

 

Import signed certificate into keystore

$ keytool -importcert -trustcacerts -keystore $BBKEYSTORE -alias tomcat -file signedcert.p7b

 

Run the PushConfigUpdates admin tool

$ /usr/local/blackboard/tools/admin/PushConfigUpdates.sh --no-restart --fast

 

Restart blackboard service

$ sudo systemctl restart blackboard

 

 

Contents of installer.properties

 

## Hostname and port numbers used when building URLS that get sent ##
##        to browsers or included in notification emails.        ##
##  These should reflect the frontend hostname and ports that are  ##
## used to access the application.                                ##
bbconfig.frontend.fullhostname=localhost.localdomain
bbconfig.frontend.portnumber=443
bbconfig.frontend.protocol=https

 

##  NOTE: this property is read at initial installation only. The  ##
##  value thereafter is set on Admin->System Config->Email Config  ##
bbconfig.admin.email=root@localhost.localdomain

 

## The SMTP host name via which Learn sends email out. This is a mandatory setting. ##
bbconfig.smtpserver.hostname=localhost
## The port on SMTP server port which Learn connects to send email. It will be protocol-default if not specified. ##
bbconfig.smtpserver.port=
##  The boolean flag to indicate whether the SMTP server requires authentication, the value can be either true or false(default)  ##
bbconfig.smtpserver.auth.required=false
bbconfig.smtpserver.username=
bbconfig.smtpserver.password=
## Connection type specifies the way how the emails be encrypted, the valid options are ##
## Default:  the emails are not encrypted.                                              ##
## StartTLS: the emails are encrypted via TLS                                          ##
## SSL:      The emails are emails via SSL                                              ##
## Any other value (including null) will be token as Default.                          ##
bbconfig.smtpserver.connectiontype=default

 

##                  tomcat developer properties                    ##
bbconfig.tomcat.debug.enable=true

 

# default passwords inside Learn, modify as desired #

 

antargs.default.users.integration.password=password
antargs.default.users.administrator.password=password
antargs.default.users.guest.password=password

 


# default postgresql passwords, as pre-configured #

 

antargs.default.vi.db.password=postgres
antargs.default.vi.stats.db.password=postgres
antargs.default.vi.report.user.password=password
bbconfig.database.admin.password=postgres
bbconfig.cs.db.cms-user.pass=postgres
bbconfig.database.server.systemuserpassword=postgres

 

## Enable caching of plugins to local file systems for performance ##
# true - Mimics B2 behavior of Learn SaaS
bbconfig.plugins.cache.enabled=true

 


# typical defaults, do not need editing #

 

bbconfig.appserver.fullhostname=localhost.localdomain

 

bbconfig.basedir=/usr/local/blackboard
bbconfig.file.license=/usr/local/bbinstaller/blackboard-license.xml
bbconfig.java.home=/usr/java/latest

 

bbconfig.database.datadir=/usr/local/bbdata
bbconfig.database.type=pgsql
bbconfig.database.server.instancename=
bbconfig.database.server.fullhostname=localhost

 

bbconfig.database.server.instancenametype.oracle=SID
bbconfig.oracle.client.drivertype=thin
bbconfig.database.indexdir.oracle=/usr/local/bbdata

 

bbconfig.unix.max.open.files=5000

 

bbconfig.inst.name=Blackboard, Inc.
bbconfig.inst.city=Washington
bbconfig.inst.state=DC
bbconfig.inst.zip=20001
bbconfig.inst.country=USA
bbconfig.inst.type=Developer

 

##      java virtual machine config - java bound processes        ##
bbconfig.min.heapsize.tomcat=2048m
bbconfig.max.heapsize.tomcat=2048m
bbconfig.max.stacksize.tomcat=1M

 

bbconfig.jvm.options.extra.tomcat=-XX:+UseCompressedOops -XX:+DoEscapeAnalysis -Xverify:none

 

bbinstaller.skip.db.comments=true

 

# end of installer.properties

 

 

Feb 11, 2019 - added Starting Block installation

Mar 12, 2019 - updated Starting Block installation and added Post Install section

Hey All! The Bb developers and admins community is awesome. Sometimes, though, we need quick answers or interactive discussion with our peers - like in the middle of an upgrade gone wrong.

 

We can build on the power of the community by having a Slack workspace for Blackboard. Your friendly developer experience team at Blackboard is building a Slack workspace for just that purpose. If you can't install Slack on your work computer, you will still be able to use the mobile app. Slack features great controls for quiet hours and snoozing notifications, getting notified for specific keywords, and topic-specific channels. Hit the link below to join up. When you do, go to the #introductions channel and say hi.

 

Join Bb Techies

 

Drop in when you get a chance.

Happy New Year, 2019!

 

After two years of presenting our intention to deprecate SOAP, I am excited to say that 2019 is the year for our deprecating Learn SOAP Web Services! You can find the full deprecation announcement at the end of this document.

 

Over the past couple years I have had a few folks ask me “Mark, why would you remove the SOAP Web Services?” and “Mark, why would you replace the SOAP Web Services with REST?” The following addresses these questions.

 

APIs can use different architectures for transfer of data from the remote server to the client software. In the case of Blackboard the two web service architectures are SOAP and REST. Historically, SOAP was the go-to messaging protocol for web services. But SOAP suffers from limitations that present concerns as we develop new services and products. These SOAP limitations also hinder you from building integrations with Blackboard products.

 

The need for lightweight web and mobile applications, firewall transparency, broad language support with easily found examples, security, and data transfer in a compact format have driven the popularity of RESTful architecture. The flexibility of REST has led us to adopt REST as our primary integration architecture for our products.

 

REST, in many use cases, offers server and client performance improvements over SOAP. These improvements decrease data access times such as page loads.

 

Finally, the developer and blackboard administrator experiences are simply better with REST applications. The developer portal allows easy documentation for documenting APIs and for managing and delivering integrations with modern authorization workflows. We also have the developer community and GitHub sites that provide discussion, source code, and examples about our REST APIs. And, to be blunt, it is easier for developers to build integrations with REST APIs than with SOAP web services.

 

Blackboard introduced Learn SOAP Web Service APIs in 2007. Since that time our Learn product has undergone substantial change. As a result SOAP is not suitable for SaaS and Ultra integrations. Since its introduction, REST has emerged as the de facto and preferred API architecture.

 

The selection of REST as the architecture behind our integration-enabling strategy enables Blackboard and our developer community to move forward with a modern, lightweight, and flexible integration architecture. For this reason SOAP is being deprecated.

 

Thank you for your interest in developing integrations for Blackboard Learn and I am looking forward to talking with you about your REST-based integrations in 2019!

 

Cheers,

-m

 

Mark O'Neil

Director, Product Management, Platform and APIs

Blackboard Inc.

 

 

 

Blackboard SOAP Web Services Deprecation Announcement

This SOAP deprecation announcement pertains to Blackboard SOAP APIs. It does not apply to the IMS Learning Information Services (LIS) SOAP Web Service-based SIS Services.

 

With the release of Blackboard Learn Q2 2019, Blackboard deprecates our SOAP APIs. Blackboard SOAP APIs will be available for two self-hosted and managed-hosted releases, starting with the release of Q2 2019 and will be removed from the Q2 2020 and corresponding SaaS releases of Blackboard Learn. We recommend that if you use Blackboard's SOAP APIs in any capacity, as an external application or as part of a Building Block, that you immediately refactor your integration using our Blackboard REST APIs.

 

We are making this change to modernize our integration model for developers through the move to REST APIs. You may read about this strategy in more detail in the below “Why SOAP Deprecation?” article on our Community site.

 

We have created several documents on our Community site to provide change guidance

 

Should you determine that your existing integration cannot be refactored using current REST APIs, please contact us at developers@blackboard.com.

 

Blackboard SOAP APIs will become unavailable with the release of Blackboard Learn Q2 2020 and it's corresponding SaaS release.

md0049252

Blackboard REST for Humans

Posted by md0049252 Sep 17, 2018

Overview:

I wrote a python library that makes consuming Blackboard REST really easy. It's very alpha, but I'm going to work on creating a three part series for the community and this is part 0.  Here's the easiest way to get started, though this does assume you have Python installed already.  Feel free to use virtual environments, I'm skipping them in this post.

 

pip install bbrest
pip install jupyterlab 
jupyter lab

 

bbrest is my library.

jupyterlab is an interactive REPL library that is mainly used for data science, but works well for our purposes.

Running 'jupyter lab' launches an interactive command line at http://localhost:8888/lab

Click the Python 3 icon to launch a Jupyter Notebook and then run the following commands:

from bbrest.bbrest import BbRest
url, key, secret = "your_url", "your_key", "your_secret"
bb = BbRest(url=url, key=key, secret=secret)

 

You now have a bb connection, which will do the following for you:

1. Get your version of Bb and only expose the REST calls you have access to.

2. Keep your session alive, automatically renewing if it expires.

3. Make some APIs way easier to use.

 

Here are some quick examples, for more, visit GitHub - mdeakyne/BbRest: Blackboard REST APIs... for humans?

#Session management
bb.expiration() #will print a human readable expiration time.
bb.calls_remaining() #will print your total calls, and call limit.

#Help with APIS
bb.User<tab> #will list calls with user
bb.GetUser(<tab>) #will list required parameters (userId=)
help(bb.GetUser) #will list documentation from Blackboard Explore APIs

#Convenience
bb.GetUser(userId='test_user') #will default to userName for users
bb.GetCourse(courseId='ENG-202') #will default to courseId for courses

bb.GetUser(userId='externalId:202020') #Can still use other specifications with : 

 

Currently, documentation is a little sparse, but you can get up and running in Python with 3 lines of code!

Here's a quick preview of the upcoming 3 part series:

 

1. REST Scripting in Python with Jupyter Lab. (Full REST setup guide)

2. REST App in Python in Flask.

3. LTI in your Flask, restricting access.

 

Hopefully, this library helps you out.  I'm happy to take feedback.

-Matt

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.

 

Hello and Welcome!

 

It's been awhile since I did a post, and I just got back recently from DevCon18. I was asked by several peers about if I have ever tried to do an Item Analysis over multiple courses. As a matter of fact, I have! A few years ago I was tasked to see if we can do an IAQ on an English Skills test. My first response was to have the instructors just run the builtin IAQ from with in the course. The response from that was that they wanted it from all English courses from lower level and higher level course (ENC1101 - ENC1102). The test they were using was exactly the same in every course: Name of the test, questions, answers. An exact copy. This was a really good start as I got a chance to manually review the test to know: How many questions? How many answers for each question? What types of questions there are? etc...

 

Once I have mapped that out I got a really good understanding of what to do when trying to collect this data. At first, the request was to know the grades of each student for this report. Ok, done. I mapped out the db tables pulled in the grades. Then, the request got bigger. They wanted to know what the student chose! Well that is a completely different approach. the original thought was that I assumed that they would look at the final scores and calculate that based of the points of each question to get a idea of the test overall and then review it further. Nope!

 

So now here comes the SQL Query and hopefully I will be able to break down the key parts:

 


SELECT 
     CM.COURSE_ID,
     CASE
          WHEN INSTR(CM.COURSE_NAME, 'INTERNET') > 0 THEN 'Internet'
          ELSE 'Face To Face'
     END COURSE_TYPE,
     T.SOURCEDID_ID AS TERM_CODE,
     T.NAME AS SEMESTER,
     U.BATCH_UID AS STUDENT_ID,
     REGEXP_REPLACE(
          REGEXP_SUBSTR(U.STUDENT_ID, '^\([[:alnum:]]+\)')
          ,'\(|\)'
          ,''
     ) COHORT,
     U.LASTNAME,
     U.FIRSTNAME,
     GM.TITLE ASSESSMENT,
     QRD.POSITION QUESTION_POSITION,
     TRIM(QAD.TITLE) QUESTION_TITLE,
          QAD.CHOICE_0,
          QAD.CHOICE_1,
          QAD.CHOICE_2,
          QAD.CHOICE_3,
     CASE QRD.STUDENT_RESPONSE
          WHEN '0' THEN QAD.CHOICE_0
          WHEN '1' THEN QAD.CHOICE_1
          WHEN '2' THEN QAD.CHOICE_2
          WHEN '3' THEN QAD.CHOICE_3
          ELSE QRD.STUDENT_RESPONSE
     END STUDENT_RESPONSE,
     QRD.CORRECT,
     A.SCORE,
     GM.POSSIBLE POINTS_POSSIBLE,
     GM.TOOL_COMPUTED_POINTS MANUAL_GRADE_OVERRIDE,
     CASE A.STATUS
          WHEN 3 THEN 'IN_PROGRESS' 
          WHEN 4 THEN 'SUSPENDED' 
          WHEN 6 THEN 'NEEDS_GRADING'
          WHEN 7 THEN 'COMPLETED' 
          WHEN 8 THEN 'IN_MORE_PROGRESS'
          WHEN 9 THEN 'NEEDS_MORE_GRADING'
     END STATUS
FROM 
     BBLEARN.COURSE_MAIN CM,
     BBLEARN.GRADEBOOK_MAIN GM,
     BBLEARN.GRADEBOOK_GRADE GG,
     BBLEARN.COURSE_USERS CU,
     BBLEARN.USERS U,
     BBLEARN.ATTEMPT A,
     (WITH xmlResultData
          AS (SELECT 
               PARENT_PK1,
               QTI_ASI_DATA_PK1,
               PK1,
               POSITION,
               BBMD_GRADE CORRECT,
               XMLTYPE(DATA, nls_charset_id('AL32UTF8')) data
               FROM BBLEARN.QTI_RESULT_DATA)
     SELECT
          x.PARENT_PK1,
          x.QTI_ASI_DATA_PK1,
          x.PK1,
          x.POSITION,
          x.CORRECT,
          REGEXP_REPLACE(
             NVL(x.data.EXTRACT('/item_result/response/response_value/text()').getStringVal(),
             x.data.EXTRACT('/item_result/response/response_value/formatted_text/text()').getStringVal())
             ,'&lt;|p&gt;|/', '') STUDENT_RESPONSE
     FROM xmlResultData x) QRD,
     (WITH xmlData
      AS (SELECT
               qad.PK1,
               qad.PARENT_PK1,
               qad.DESCRIPTION,
               qad.POSITION,
               XMLTYPE(qad.DATA, nls_charset_id('AL32UTF8')) data
           FROM BBLEARN.QTI_ASI_DATA qad)
     SELECT
          x.PK1,
          x.PARENT_PK1,
          x.POSITION,
          x.DESCRIPTION TITLE,
          TRIM(
               REGEXP_REPLACE(
                  REGEXP_REPLACE(
                      x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[1]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal(),
                      '&quot;|&apos;|&lt;|p&gt;|/',
                      ''
                  ),
               '[' || CHR(10) || CHR(13) || ']',
               ' '
            )
          ) CHOICE_0,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[2]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_1,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[3]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_2,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[4]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_3
FROM xmlData x) QAD,
     BBLEARN.COURSE_TERM CT,
     BBLEARN.TERM T
WHERE CM.PK1 = GM.CRSMAIN_PK1
     AND GM.PK1 = GG.GRADEBOOK_MAIN_PK1
     AND CU.PK1 = GG.COURSE_USERS_PK1
     AND U.PK1 = CU.USERS_PK1
     AND A.QTI_RESULT_DATA_PK1 + 1 = QRD.PARENT_PK1
     AND QAD.PK1 = QRD.QTI_ASI_DATA_PK1
     AND CM.PK1 = CT.CRSMAIN_PK1
     AND T.PK1 = CT.TERM_PK1
     AND GG.PK1 = A.GRADEBOOK_GRADE_PK1
     AND CU.ROLE = 'S'
     AND REGEXP_LIKE(GM.title, '^M\w+\s+English Skills Test\s+#[1|2|3].*$')
     AND GM.title NOT LIKE '%Final%'
     AND T.SOURCEDID_ID >= 20163
     AND REGEXP_LIKE(CM.COURSE_ID, '^ENC110(1|2)-\d{6}$')
     AND ROWNUM<=100
ORDER BY CM.COURSE_ID, U.BATCH_UID, GM.TITLE, QRD.POSITION ASC;




 

 

 

 

 

WHAT THE?????

Seriously, that took awhile to compose. So, a quick little story. So after I started poking around the database tables I came across the QTI_ASI_DATA table. This is the table that houses the assessments. Yep, old Angel users, this is the same type of table but slightly different. So after following the foreign key trails I ended up at the QTI_RESULT_DATA. I came across this DATA field with a CLOB. I first ignored it. I poke around more but could not find the responses from each question.....So I started reaching about the CLOB data type and turns out if you extract that data, you get an xml snapshot of the test response from a particular attempt! So with some good ole hacker'y I managed to extract the data out!

Breakdown, from the top

Getting the Choices

 


CASE QRD.STUDENT_RESPONSE
     WHEN '0' THEN QAD.CHOICE_0
     WHEN '1' THEN QAD.CHOICE_1
     WHEN '2' THEN QAD.CHOICE_2
     WHEN '3' THEN QAD.CHOICE_3
     ELSE QRD.STUDENT_RESPONSE
END STUDENT_RESPONSE,


 

This above snippet show how to provide the multiple choice 'choices'. This helps knowing the max amount of possible choices in the test. I am pulling from my aliased QRD (QTI_RESULT_DATA) table then mapping it to the QAD aliased table. This allowed me to build the test back into my result data set!

But how to get the data out of the CLOB?

 


     (WITH xmlData
      AS (SELECT
               qad.PK1,
               qad.PARENT_PK1,
               qad.DESCRIPTION,
               qad.POSITION,
               XMLTYPE(qad.DATA, nls_charset_id('AL32UTF8')) data
           FROM BBLEARN.QTI_ASI_DATA qad)
     SELECT
          x.PK1,
          x.PARENT_PK1,
          x.POSITION,
          x.DESCRIPTION TITLE,
          TRIM(
               REGEXP_REPLACE(
                  REGEXP_REPLACE(
                      x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[1]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal(),
                      '&quot;|&apos;|&lt;|p&gt;|/',
                      ''
                  ),
               '[' || CHR(10) || CHR(13) || ']',
               ' '
            )
          ) CHOICE_0,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[2]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_1,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[3]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_2,

          TRIM(REGEXP_REPLACE(
               REGEXP_REPLACE(
                   x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[4]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringVal()
                   ,'&quot;|&apos;|&lt;|p&gt;|/'
                   , ''
               )
          ,'[' || CHR(10) || CHR(13) || ']',' ')) CHOICE_3
FROM xmlData x) QAD,




 

So this is done the cast a selected data set as xml, then telling it to extract the data via an xpath. I have some convenience trim and replace functions to help clean up the extract data as it is xml with html entities. This helped when exporting this data set to an csv file to be imported into Excel.

 

The actual part is here:

 

     (WITH xmlData
      AS (SELECT
               qad.PK1,
               qad.PARENT_PK1,
               qad.DESCRIPTION,
               qad.POSITION,
               XMLTYPE(qad.DATA, nls_charset_id('AL32UTF8')) data
           FROM BBLEARN.QTI_ASI_DATA qad)

 

The WITH <alias> AS SELECT statement is what does the casting of the DATA field by the use of XMLTYPE. The major key note here is the charset! This took me awhile to find but this is what helps in extracting the data out.

Now onto the fancy stuff

 


     (WITH xmlResultData
          AS (SELECT 
               PARENT_PK1,
               QTI_ASI_DATA_PK1,
               PK1,
               POSITION,
               BBMD_GRADE CORRECT,
               XMLTYPE(DATA, nls_charset_id('AL32UTF8')) data
               FROM BBLEARN.QTI_RESULT_DATA)
     SELECT
          x.PARENT_PK1,
          x.QTI_ASI_DATA_PK1,
          x.PK1,
          x.POSITION,
          x.CORRECT,
          REGEXP_REPLACE(
             NVL(x.data.EXTRACT('/item_result/response/response_value/text()').getStringVal(),
             x.data.EXTRACT('/item_result/response/response_value/formatted_text/text()').getStringVal())
             ,'&lt;|p&gt;|/', '') STUDENT_RESPONSE
     FROM xmlResultData x) QRD,



 

Same as with QAD, we need to do the same for QRD.

 

x.data.EXTRACT('/item/presentation/flow/flow/response_lid/render_choice/flow_label[1]/response_label/flow_mat/material/mat_extension/mat_formattedtext/text()').getStringValue(),



 

The extract method is what allows you to use the xpath to drill down to the data you what to show. You can get more information here: IMS Question & Test Interoperability v1.2 QTILite Specification | IMS Global Learning Consortium on the QTI IMS1.x Specification.

 

Getting the data across multiple courses.

 



WHERE CM.PK1 = GM.CRSMAIN_PK1
     AND GM.PK1 = GG.GRADEBOOK_MAIN_PK1
     AND CU.PK1 = GG.COURSE_USERS_PK1
     AND U.PK1 = CU.USERS_PK1
     AND A.QTI_RESULT_DATA_PK1 + 1 = QRD.PARENT_PK1
     AND QAD.PK1 = QRD.QTI_ASI_DATA_PK1
     AND CM.PK1 = CT.CRSMAIN_PK1
     AND T.PK1 = CT.TERM_PK1
     AND GG.PK1 = A.GRADEBOOK_GRADE_PK1
     AND CU.ROLE = 'S'
     AND REGEXP_LIKE(GM.title, '^M\w+\s+English Skills Test\s+#[1|2|3].*$')
     AND GM.title NOT LIKE '%Final%'
     AND T.SOURCEDID_ID >= 20163
     AND REGEXP_LIKE(CM.COURSE_ID, '^ENC110(1|2)-\d{6}$')
     AND ROWNUM<=100
ORDER BY CM.COURSE_ID, U.BATCH_UID, GM.TITLE, QRD.POSITION ASC;




 

Now, usually I always tend to do LEFT JOINS, they tend to be more accurate and time efficient. But, the data being pull has some oddities in such that; not all choice would the same amount of choices. I believe this example I lucked out and they all ended up with the same amount. Obviously, you will need to modify the GM.TITLE, TERM (if applicable), and the COURSE_ID to match  your specific need. Note: REMOVE ROWNUM , I left this in this example as when you are running tests, you want to make sure that you start off with minimal results then increase when you are more confident in your results. Once completed, remove the rownum to get all.

 

Conclusion

I would like to thank you for sticking through this blog post and that if you have any questions, feel free to contact me via community!

 

Thanks,

 

Mike

Hello,

Who does not feel frustrated using the "Volume Exclusion" tool and does not have the Learn buttons also deleted? Or by using a course as a base, replicating this content for numerous other courses using the "Copy Course" tool. These in turn, also depending on the deletion of buttons before the replica.

Having this and other scenarios I share with you the post (text in Portuguese).

Screen Shot 2018-03-25 at 5.53.04 PM.pngIn an upcoming hackathon at Grand Valley State University students will be asked to innovate and code for Blackboard Learn. In addition to REST API category, a basic HTML category of the competition will use Community Engagement modules.  A new app was created to encourage development of the modules without the need to grant access to a Blackboard Learn system.

While there are a few ways to run the Blackboard Learn system for free or almost without cost, the new wizard makes the development and debugging easy.

This is the app:

Community System: HTML Module Wizard


I've been working in a proof of concept application to use LDAP to authenticate users in Blackboard Collaborate using the simplest possible approach. Now I have something working and I want to share it with the community.

 

First of all I'm not a PHP programmer, so the code you'll see needs a lot of improvement and is not production ready, but I think is enough in order to understand the concept. I've choosen PHP because of the simple LDAP interaction that it has out of the box.

 

The concept is that you can create a login page that binds the user with the LDAP server, and if the binding is successful then it creates a LTI request with the user data in a unique context and redirect the user to the management interface.

 

Here you can watch a demo and code walkthrough, (sorry, it is in Spanish, I'll upload an English version as soon as I have enough time) : Demo LDAP-Collaborate - eLearning Media

 

The code used in the video is available in Github: GitHub - eLearningMedia/LDAP-Collaborate-login: Simple application to manage Blackboard Collaborate sessions and recordi…

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.

 

I want to share my experience creating a "blank page" content item in the course TOC programatically.

 

My motivation was to add the Collaborate recordings download links from a course to a content in order to keep them available after restoring the course in other instance of Learn after archiving, because the pk1 and the Collaborate plugin tables are not kept.

 

The core of the solution is this code:

 

  CourseToc newToc = new CourseToc();

  newToc.setTargetType(Target.CONTENT_ITEM);
  newToc.setCourseId(course.getId());
  newToc.setLabel("Recordings");
  newToc.setLaunchInNewWindow(false);
  newToc.setIsEnabled(true);
  newToc.setAllowGuests(false);

  Content content = new Content();

  content.setTitle("Recordings");
  content.setCourseId(course.getId());
  content.setIsAvailable(true);
  content.setAllowGuests(false);
  content.setIsFolder(false);
  content.setLaunchInNewWindow(false);
  content.setContentHandler("resource/x-bb-blankpage");
  content.setBody(FormattedText.toFormattedText(contentString));

  ContentDbPersister.Default.getInstance().persist(content);

  newToc.setContentId(content.getId());

  CourseTocDbPersister.Default.getInstance().persist(newToc );

  CourseMapManagerFactory.getInstance().invalidateCache(course.getId());

 

The contentString variable contains the HTML to show in the blank page content.

 

Hope this will help somebody who want to create any kind of content programatically, not just my particular user case.

Hello everyone! I made a comment in a post a while back about using JS Hacks to override some CSS rules that the new 2016 Theme enforces. I have a few people ask me about this and I though this would be a good opportunity to write up a small post about it.

 

 

Why won't my custom theme stick!

If you are using the new 2016 theme, you may have notice that some things are very opinionated and you want to change some things. So you download the theme and start hacking away at the CSS files. You finally re-upload your theme and....the rules are not sticking!!! What gives?!?!

 

How do I overcome this issue?

Well what is happening is that there is a them1.css file that is being injection during the page render time. Since the last rule set wins when it comes to css, your beautiful css rules are now squashed by Blackboard! Bummer!

 

Conquering the CSS Takeover!

Well, there is another way! You can make all the css rules that are being squashed, into another css file. Then you can place either as an attachment via JS Hacks, or what I have done is place this file in the Content Collection and made it publicly available. Now I can copy this URI path and use it in my diabolical plan to take over the world! Blackboard!

 

 

Here is the JS Hack:

 

 

<!--

    your.blackboard.com = your instance of Blackboard Learn

    CC_FOLDER = is just a folder created in the Content Collection

    your_theme1 = the id you wish you give your element

 -->

<script type="text/javascript">

// https://your.blackboard.com/bbcswebdav/institution/CC_FOLDER/theme/theme1.css

    Event.observe(document,"dom:loaded", function() {

        //Your javascript goes here.

        // console.log($$('head')[0])

        $$('head')[0].insert({

            bottom: new Element('link', {

                href: '/bbcswebdav/institution/CC_FOLDER/theme/theme1.css',

                type: 'text/css',

                rel: 'stylesheet',

                id: 'your_theme1'

            })

        });

    });

</script>


Hello, I recently just had to run a report via Open DB and supply some Learn Information to a vendor that will be creating a PoC. I was tasked on getting this information but the information of course would violate FERPA. So I had to come up with a way to mask the user information to still supply an accurate model but protect the user information at the same time. So I decided to use an old python script that converted CSV files to XLSX and make some modifications.


 

I used openpyxl to create the Excel XLSX files, Docopt to make the CLI creation easy, and some built in core modules: csv, json, and random.


 

The main challenge was that I had two reports from Open Db that correlated between Activity and Gradebook Data by the USER_ID. I ended up doing some Python wizardry and use a global (gasp!! what globals in Python, your fired!) Anyways, yes the use of a global in Python is frowned upon, but hey it got the job done! That’s what re-factoring is for...right?


 

Anyways, I thought I would share this application and share my work and hopefully see how useful this can be. I plan on implementing a way to connect directly to Open Db (Already have it done in another project) and then add in some common functionality: Mongo DB Storage, Better Configuration Methods, etc.


 

You are all welcome to use, share, clone, hack, and destroy this code here:

https://github.com/elmiguel/CSV2XLSX


 

You will just need to query and save your own data sets… Luckily for you, I will share my queries:

 

Activity Accumulator:


 

SELECT AA.PK1
,AA.EVENT_TYPE
,U.USER_ID
,CM.BATCH_UID
,AA.GROUP_PK1
,AA.FORUM_PK1
,AA.INTERNAL_HANDLE
,AA.CONTENT_PK1
,AA.DATA
,AA.TIMESTAMP
,AA.STATUS
,AA.MESSAGES
,AA.SESSION_ID
FROM BBLEARN.ACTIVITY_ACCUMULATOR AA
INNER JOIN BBLEARN.USERS U
ON AA.USER_PK1 = U.PK1
INNER JOIN BBLEARN.COURSE_MAIN CM
ON AA.COURSE_PK1 = CM.PK1
WHERE CM.COURSE_ID IN (‘LIST COURSE_IDS HERE’
)
ORDER BY AA.TIMESTAMP DESC;







 

 

 
  Gradebook Data:


 

SELECT
CM.COURSE_ID,
U.USER_ID,
U.FIRSTNAME,
U.LASTNAME,
GM.TITLE,
GM.DUE_DATE,
A.SCORE,
CASE A.STATUS
WHEN 1 THEN 'NOT_ATTEMPTED'  
WHEN 2 THEN 'ABANDONED'
WHEN 3 THEN 'IN_PROGRESS'  
WHEN 4 THEN 'SUSPENDED'
WHEN 5 THEN 'CANCELLED '
WHEN 6 THEN 'NEEDS_GRADING'  
WHEN 7 THEN 'COMPLETED'
WHEN 8 THEN 'IN_MORE_PROGRESS'  
WHEN 9 THEN 'NEEDS_MORE_GRADING'
ELSE 'NO STATUS'
END AS STATUS,
A.ATTEMPT_DATE,
A.FIRST_GRADED_DATE,
A.LAST_GRADED_DATE,
A.DATE_ADDED,
A.DATE_MODIFIED,
A.LATEST_IND
-- A.STUDENT_SUBMISSION
-- A.PK1 ATTEMPT_PK1,
-- A.QTI_RESULT_DATA_PK1,
-- GG.GRADEBOOK_MAIN_PK1,
-- GG.PK1 GG_PK1
FROM GRADEBOOK_MAIN GM  
INNER JOIN GRADEBOOK_GRADE GG
ON GG.GRADEBOOK_MAIN_PK1 = GM.PK1
INNER JOIN ATTEMPT A
ON A.GRADEBOOK_GRADE_PK1 = GG.PK1
INNER JOIN COURSE_USERS CU
ON GG.COURSE_USERS_PK1 = CU.PK1
INNER JOIN COURSE_MAIN CM
ON CU.CRSMAIN_PK1 = CM.PK1
INNER JOIN USERS U
ON CU.USERS_PK1 = U.PK1
WHERE CM.COURSE_ID IN (‘LIST COURSE_IDS HERE’);







mkauffman

ALL FILES No More

Posted by mkauffman Apr 18, 2017

I'm writing this to announce an upcoming requirement for all Building Blocks (B2s). Currently Blackboard allows the following in a B2's bb-manifest.xml file.

<permission type="java.io.FilePermission" name="&amp;lt;&amp;lt;ALL FILES&amp;gt;&amp;gt;" actions="read,write,delete"/>

The above allows the B2 to write to anywhere on the host file system.

 

Because of the security implications,  Blackboard is asking all B2 developers to remove that permission from bb-manifest.xml by Q4 2017. We'll be communicating this out here on our Community site, via our Partner Newsletter, in an Announcement on Behind the Blackboard, etc.

 

Below is a set of permissions that opens up everything in the Blackboard directories. These are almost as 'bad' as <<ALL FILES>> in that they let you overwrite other Blackboard files and content, for example anything in the vi directory, but are far better than allowing changes to any file in the file system. /- indicates every file and directory beneath the specified folder. Once you get your Building Block functioning with the following, then we recommend reducing the set of permissions to only those directories/files that it needs to access, and only those actions that are necessary.

 

<permission type="java.io.FilePermission" name="${java.home}/-" actions="read"/>

<permission type="java.io.FilePermission" name="BB_HOME/-" actions="read,write,delete"/>

<permission type="java.io.FilePermission" name="BB_HOME/apps/tomcat/temp/-"  actions="read,write,delete" />

<permission type="java.io.FilePermission" name="BB_CONTENT/-" actions="read,write,delete"/>

 

Here is an option for logging only that is less promiscuous, if your B2 doesn't need to write elsewhere in the BB directories.

<permission type="java.io.FilePermission" name="BB_HOME/logs/-" actions="read,write,delete"/>

 

Following is a link to sample code that uses logback to write to blackboard/logs/custom and blackboard/logs/plugins/<vendor_id>-<handle>/

 

blackboard/logs/plugins/<vendor_id>-<handle> is the directory that B2s should be writing to from here on out. It's the only B2-specific directory that makes the B2 specific logs available to the Kibana log visualizer in SaaS.  GitHub - mark-b-kauffman/bbdn-bblogbackb2: Demo the use of Logback to create log files.

 

Please plan to address this soon so that your Building Block will be able to be installed in Q4 2017 and later.