Helium Developer Guide

figure-31 figure-1

Overview

Why did we build Helium?

Building and delivering highly differentiated and impactful learning experiences shouldn’t be difficult. Turning your vision into reality shouldn’t be constrained by the technical limitations of your software platform. Our mission for Helium is to empower developers and designers to bring their visions to life through a simple and powerful framework that enables full customization and extensibility of the Thought Industries Customer Learning Cloud.

Ultimately we want to make it easy to:

  • Customize the web site & learner experience to achieve design goals
  • Modify the structure, appearance and functionality of courses, assessments, tests, and more
  • Incorporate external data into learner-facing components like dashboards and catalogs
  • Utilize internally-developed functionality and seamlessly integrate into the learner experience
  • Deliver in-the-moment training natively across multiple platforms (beyond Web/HTML)

What is Helium?

Helium is a frontend web development framework for building highly contextual, dynamic and personalized learning experiences. Helium provides you with a complete library of UI Components, Hooks, and Utilities that make building custom learning experiences fast, easy, and fun.

Helium utilizes a modern technology stack that includes React, GraphQL, and Tailwind CSS to provide an exceptional developer experience. The out-of-the-box UI components make getting started easy and with GraphQL getting to the data you need is intuitive and fast.

The code for Helium is fully open source! All of the source code in available in the Helium GitHub Repository.

Benefits of Helium:

  • Get Started Quickly – Helium provides a starter application that is simple to use. The starter application interfaces with the Customer Learning Cloud seamlessly and delivers a custom learning experience right out of the box. The starter application is based on the Helium framework and styled with the Tailwind CSS utility package.
  • Design Flexibility – Helium separates the section of your website that is visible to learners, or the “head,” from the component that manages data and functionality. This “headless” architecture allows you complete control over the visual appearance and functionality giving you complete design freedom.
  • Easier Personalization – Helium’s React-based framework facilitates the ability to display dynamic content and tailor the learners’ experience by more easily accessing and incorporating relevant data – wherever the data exists.
  • Build Highly Differentiated Experiences Faster –  The Helium framework uses a modern tech stack that speeds up the development process. It’s seamlessly integrated with the Customer Learning Cloud hosting environment and includes all of the necessary tools and utilities for local development along with utilities for staging and production code management. Helium comes with out-of-the-box, code accessible React components, hooks, tools, utilities and a starter application.
  • Optimized Performance – Helium is developed on cutting-edge technology that makes heavy use of server side rendering. As a result, the bandwidth required to load a website is significantly reduced. This also results in increases to your website’s Core Web Vitals, as well as an increase in organic search traffic.

Limitations and Considerations

  • Helium requires technical resources and familiarity with JavaScript and the React framework.
  • Helium is designed to run on the Thought Industries Enterprise Learning Cloud platform. Therefore, an Enterprise Learning Cloud account is necessary.
  • We are continually adding new components and functionality so please be sure to check documentation frequently as well as the Helium community forums for updates.

Was this helpful?

Getting Started

Requirements

  • Ti Enterprise Learning Cloud account and API key
  • Node 14 or higher
  • NPM 8.6.0 or higher

Helium Starter App

Helium comes out-of-the-box with a styled starter app. The starter app currently has three pages built in: a homepage, a catalog, and a dashboard. From there, you’re free to create additional pages and routes for your Helium app. Additionally, there are some sample CTA’s built into the sample app to showcase what’s possible with Helium which you can also customize or delete. As the development of our graphQL API moves forward, we’ll continue to add additional out-of-the-box pages and functionality to the starter app.

Installation and Set Up

  • Install with $ npm init helium-app
  • Enter the directory inside the folder you want your helium app to live in
  • Run $ cd [directory name] and then run $ npm install
  • Finally, run $ npm run authenticate

Authentication

  • Run $ npm run authenticate and complete the following steps
    • Enter the url of a Ti learning instance
    • Enter the API key for this instance (Available from Admin homepage under Settings > Security > API Key)
    • Enter a nickname for this instance
    • Enter an email of a test user for this instance. When you preview your learning instance you’ll see the view accessible to this test user. For example, you’ll only see courses that this user has access to.
    • Determine if you like to add another instance (y/n)
    • Note: If you have both a sandbox and production instance we recommend adding both with this command so you can deploy Helium apps to your sandbox before you deploy to production.
  • a ti-config.json file will get generated which can be edited later. For example, if you want to change the test user, you can just edit the ti-config.json file.
  • Note: Your instance’s brand appearance (color, logo, etc.) and widget translations are readily available to use at this point.

Deployment

In production, Helium runs on globally distributed Cloudflare workers that are optimized for speed and performance regardless of where the client is located.

To deploy, run $ npm run deploy [instance name] where [instance name] is the nickname you entered when running $ npm run authenticate.

We’d also like to call out some key points:

  • If the Helium feature flag is not turned on for your learning instance, you won’t be able to deploy your Helium site
  • It may take some time for your first Helium deployment to register to your learning instance URL
  • If you want to delete a deployed Helium page, simply remove the folder or file that represents the page from your Helium app and run $ npm run deploy [instance name]
  • Helium works on a page by page basis, meaning it’s possible (and likely) that some of your pages will be Helium pages and some of your pages will be powered by the existing Thought Industries platform. For example, your homepage might be powered by Helium while your catalog page is not
  • It’s not possible to utilize Helium and the existing Thought Industries platform on the same page. You can’t use Thought Industries widgets on Helium pages.

Was this helpful?

Framework

Server Side Rendering

What is Server Side Rendering (SSR)?

SSR is technique used to improve the load time of web apps. Most web apps built today are Single Page Applications (SPA) where all the code for the site is built and served on a single page. This makes for a great and intuitive user experience but bundling all of this code and loading it on to a single page is slow. This leads to poor performing SEO and a degraded user experience.

SSR solves this problem in a creative way. Instead of sending all the code at once to the client browser, SSR sends a barebones structure of the web app to the client which then renders very quickly. At the same time, the entire web app code is sent to a server which renders the code as well (hence the name Server Side Rendering). Once the web app is generated on the server, the server then hydrates the barebones structure making the web app fully functional on the client.

Helium and SSR

Helium utilizes SSR. It’s not required but we do recommend it. Below is an example of how you can utilize SSR in your Helium app with the FeaturedContent component

import React from 'react';
import { useTranslation } from 'react-i18next';
import {
  FeaturedContent,
  ContentTileStandardLayout,
  FeaturedContentContentItem
} from '@thoughtindustries/featured-content';
import {
  hydrateContent,
  useCatalogQuery,
  useAddResourceToQueueMutation
} from '@thoughtindustries/content';

export { Page };

function Page() {
  const { i18n } = useTranslation();
  const [addResourceToQueue] = useAddResourceToQueueMutation();
  const handleAddedToQueue = (item: FeaturedContentContentItem): Promise<boolean | void> =>
    item.displayCourse
      ? addResourceToQueue({ variables: { resourceId: item.displayCourse } }).then()
      : Promise.resolve(undefined);

  const { data, loading, error } = useCatalogQuery();

  let content;

  if (data) {
    content = data.CatalogQuery.contentItems.map((item, index) => {
      const hydratedItem = hydrateContent(i18n, item);
      const { authors, description, href, ...restItemProps } = hydratedItem;
      const transformedItem = {
        ...restItemProps,
        authors,
        shortDescription: description && `${description.substring(0, 75)} ...`,
        linkUrl: href
      };
      return <ContentTileStandardLayout.Item key={`item-${index}`} {...transformedItem} />;
    });
  }

  return (
    <div className="px-4">
      <FeaturedContent>
        <ContentTileStandardLayout
          desktopColumnCount={4}
          onAddedToQueue={handleAddedToQueue}
        >
          {content}
        </ContentTileStandardLayout>
      </FeaturedContent>
    </div>
  );
}

Data and GraphQL

All data in Helium flows through GraphQL, an extremely modern and open-source data query and manipulation language for APIs. GraphQL APIs differs from REST API’s in various ways, but at the heart of it, GraphQL has greater flexibility and efficiency in terms of data access and management. More can be read about the comparison between the two here.

There are two ways to interact with GraphQL in Helium apps: the more general useQuery hook and other more specific hooks for specific data groups. An example of these more specific hooks is the useCatalogQuery hook which fetches specific data about catalog contents. Any hook provided by the Helium packages can replicated with useQuery–the provided hooks exist to make it easier to start and maintain Helium apps.

Helium GraphQL Documentation

Fetching Content with GraphQL

In our admin experience we offer several different content types: Courses, Videos, Blogs, Scorms, Articles, and more. Although these objects are presented differently, on the backend many of them overlap. It’s important to understand how content data is structured so that you can fetch the appropriate data with our graphQL API.

Courses

Courses are one of our most complex content types. At its root each course belongs to a courseGroup. Courses then have sections, sections have lessons, and lessons have topics. Examples of topics are quizzes, tests, or text pages. To get the id of the courses in your learning instance you can call CatalogContent. You would typically use this query to build your CatalogPage.

CatalogContent(page: 1) {
  contentItems {
    title
    displayCourse
  }
}

You can then use the ids returned from CatalogContent to call CourseById to fetch the various sections, lessons, and topics that a course has. This will give the outline of the course and you can also fetch more data about a specific course than you can with the CatalogContent query.

CourseById(id: “sample-id”) {
  Sections {
    Lessons {
      Topics {
        … on ArticlePage {
          title
          id
          type
        }
        … on QuizPage {
          title
          id
          type
        }
      }
    }
  }
}

Now that you have the ids of the various topics in the course you can call the Pages query to get more data about the specific topics themselves. For example, if you have a quiz topic with the Pages query you can get the quiz questions, the answers, and how long it usually takes to complete the quiz.

Pages(identifiers: [String!]!) {
  ... on QuizPage {
    title
    questions {
      body
      choices {
        value
        correct
      }
    }
  }
}
Videos, Blogs, Articles:

Under the hood, Videos, Blogs, and Articles are also all stored as course objects but they have just one topic associated with them. Fetching the data for a Blog is very similar to fetching data for a normal course. You’d follow a similar pattern of calling CatalogContent, then calling CourseById, and then calling Pages. Blogs, Videos, and Articles are all available under the ArticlePage type.

query Pages($identifiers: [String!]!) {
  Pages(identifiers: $identifiers) {
    ...on ArticlePage {
      videoAsset
      languages {
        language
        label
        title
        subtitle
        body
        copyright
      }
    }
  }
}
ILT’s

ILTs or Instructor Led Training is another content type and is also stored as courseGroup objects. Most of the information surrounding ILT’s is at the CourseById level and doesn’t require you to call the Pages query. With ILTs, you can have one ILT with multiple sessions. This structure corresponds to one parent courseGroup and then multiple child courses for each session. Here is an example of how you could fetch an ILT with multiple sessions:

query CourseById($id: ID!) {
  CourseById(id: $id) {
    title
    slug
    courseGroup {
      description
      asset
      courses {
        title
        courseStartDate
        courseEndDate
      }
    }
  }    
}


Working with the Quiz Object

There’s more moving pieces with a quiz then compared to fetching a text page for example. With a quiz, you have to both fetch data and then allow the user to send data back in the form of a quiz submission. We’ll walk you through the various API calls you’ll need to use do that here.

Fetching the Quiz Questions

The first order of business is actually getting the quiz questions to show the users. We’ll do that with the Pages query.

query Pages($identifiers: [String!]!) {
       Pages(identifiers: $identifiers) {
           ... on QuizPage {
               title
               questions {
                   body
                   choices {
                       value
                       correct
                   }
               }
           }
       }
}
Creating an Assessment Attempt

Next, we need to let the user create a quiz attempt. We’ll do that with the LoadAssessmentAttemptWithQuestions query. Given the headless nature of Helium, the choice is completely up to you when you want to call this query. Note: most users that land on a quiz page will usually submit a quiz, so it might make sense to create an assessment attempt as soon as the user lands on a quiz page. You could also do it when the user answers the first question.

query {
 LoadAssessmentAttemptWithQuestions(
   id: $id
   courseId: $courseId
   topicType: $topicType
 ) {
   id
   status
   grade
 }
}
Updating the Assessment Attempt

Now that we have the assessment attempt, we need to update it each time a user answers a question. We’ll use the UpdateAssessment mutation for this.

mutation UpdateAssessmentAttempt(
  $activeQuestion: QuestionInput,
  $assessmentAttempt: AssessmentAttemptInput
) {
  UpdateAssessmentAttempt(
    activeQuestion: $activeQuestion,
    assessmentAttempt: $assessmentAttempt
  ) {
      id
      grade
  }
}

// SAMPLE VARIABLES
{
    "assessmentAttempt": {
      "id": "{{ id from LoadAssessmentAttemptWithQuestions }}",
      "status": "started"
    },
    "activeQuestion": {
      "body": "<p>Who is the current quarterback for the Eagles?</p>",
      "mustSelectAllCorrectChoices": true,
      "selectedChoice": {
        "value": "Jalen Hurts",
        "correct": true
      }
    }
}
Submitting the Assessment Attempt

Awesome, the user has finished the quiz and is ready to click the submit button. To submit the quiz submission, we’ll call the UpdateAssessment mutation one last time

mutation UpdateAssessmentAttempt(
  $assessmentAttempt: AssessmentAttemptInput
) {
  UpdateAssessmentAttempt(
    assessmentAttempt: $assessmentAttempt
  ) {
      id
      grade
  }
}

// SAMPLE VARIABLES
{
    "assessmentAttempt": {
        "id": "{{ id from LoadAssessmentAttemptWithQuestions }}",
        "status": "finished"
    }
}

GraphQL Rate Limits

In order for a request to be processed, there are 3 limitations that must be met:
– A single query must not exceed a calculated complexity of 1000 points.
– Each instance is allotted 100,000 points per minute. This is not on a rolling basis; the points allowance resets 60 seconds from the first request.
– The instance can not exceed 1000 requests per minute, regardless of complexity or points allowance.

Helium is meant to scale with your learning instance. If you have any concerns on limitations, please reach out to us.

Calculating Query Complexity

Each requested property costs 1 point by default.

// Query example
query {
    CurrentUser {
        firstName
        lastName
    }
}

// 1 (firstName) + 1 (lastName) = a 2 point query

Certain queries types, such as `CourseViews` or `Courses` may have an additional cost as they require more resource-intensive queries.

// Query Example
query {
    CatalogContent {
        id
        title
        slug
    }
}

// 1(id) + 1(title) + 1(slug) + 3(performance cost) = a 6 point query
Additional Rate Limiting Information

Information related to points can be found in the following headers
X-RateLimit-Remaining: Number of allotted points remaining in the current duration
X-RateLimit-Reset: Timestamp at which point the allotted points will reset
Retry-After: Received only after exceeding a limit, denotes the number of seconds to wait before attempting the request again

Data Reporting with Helium

We are currently building out native data reporting support on the Thought Industries platform for Helium. A list of our currently supported data reporting can be found on our graphQL documentation. Because Helium is headless, we also encourage you to investigate other data reporting tools that fit your needs if the data reporting you’re looking for isn’t currently supported in Helium.

What is Tailwind CSS?

Tailwind CSS is a utility first CSS framework that is fast and built to scale. Tailwinds readable classes make it easier and faster for developers to understand each other’s code. There are many benefits to Tailwind but the largest one is that you no longer have to write your own CSS code. More can be learned about Tailwind and its benefits here.

Tailwind comes out of the box with Helium and is readily available for you to use with your own Helium project.

Helpful Commands

npm run authenticate [-k]

Authenticate a new project by validating against your Thought Industries instance and writing you ti-config.json file.

Options:
-k, -insecure (optional): used when the Thought Industries instance is behind an untrusted SSL certificate (e.g. local development).


npm run deploy <i> [-k]

Builds and deploys the project.

Options:
-i, -instance (required): which instance from your ti-config.json file to be used, as noted by the instance nickname provided during installation
-k, -insecure (optional): used when the Thought Industries instance is behind an untrusted SSL certificate (e.g. local development).


npm run dev [i] [p] [-k]

Starts up the development server for your project

Options:
-i, -instance (optional): which instance from your ti-config.json file to be used, as noted by the instance nickname provided during installation
-p, -port (optional): Which port the server should listen on
-k, -insecure (optional): used when the Thought Industries instance is behind an untrusted SSL certificate (e.g. local development).


npm run update-translations [i] [-k]

Fetches translation from your Thought Industries instance and updates the locales/translations-source.json and locales/translations.json files.

Options:
-i, -instance (required): which instance from your ti-config.json file to be used, as noted by the instance nickname provided during installation
-k, -insecure (optional): used when the Thought Industries instance is behind an untrusted SSL certificate (e.g. local development).

Project Breakdown

/components

This is where you can create your own custom react components! We’ve include a few of our standard ones to get you started: feel free to use them, customize or create your own.

/pages

Helium uses a file based routing system as explained below.

The pages directory is where you build out different pages of your Helium site and handle routing. Every folder within the /pages directory corresponds to a different subdirectory of your url. If your learning instance’s url is learn.com, creating a folder called test in /pages and a file named index.page.tsx in /pages/test will create a route on learn.com/test where the contents of index.page.tsx will be served. Additionally, if you want to create another route on top of test, you can create another file in /pages/test following a similar pattern. For example, creating a file named custom.page.tsx in /pages/test would create a route at learn.com/test/custom.

It’s also useful to know how to remove Helium pages. Say you deployed a page to Helium and for some reason you want to take it down. Simply remove the corresponding folder or file and re-run $ npm run deploy.

Note: Your Thought Industries Learning Instance comes with several pre-built routes such as /catalog /register and /support. If you create a folder in /pages called /catalog with a file named index.page.tsx, the content of that file will be served at learn.com/catalog.

/locales

The only file in this directory is translations.json which handles translations in your helium app. Any translation you set up in the TI Admin Dashboard get’s converted here so your translations will remain consistent. If you ever change any translations on the Admin side, simply run $ npm run update-translations and the file will update.

/server

This folders contains a simple file for started a local web server.

/vendor

This folder contains code that allows you to publish MDX files as pages on your Helium site. MDX files are Markdown files that also allow you to use JSX with markdown. You can read more about MDX here.

For example, if you create a directory called /markdown-example with a file called index.page.mdx, that MDX file will be served at example.com/markdown-example.

Components

This is a high level overview of some of the main Helium components currently available. For a complete, up-to-date list of all Helium Components and more in-depth, technical documentation please reference our Storybook component documentation.

@thoughtindustries/catalog

The Catalog allows learners to search and browse for content. It is a central component to any online learning platform.


@thoughtindustries/content

A collection of content specific resources for @thoughtindustries/helium UI components. It comes with typings and React Hooks for making GraphQL queries and mutations, as well as utilities for content hydration.


@thoughtindustries/featured-content

Featured Content components add real power to your platform page creation. These components let you display courses, microlearning, and products in neat blocks. Configure them to show static content or set them up to dynamically exhibit content based on search queries or tags.


@thoughtindustries/dashboard-stats

The Dashboard Stats component displays a high level view of how many courses the user has access to, as well as how many threads/comments (“collaborations”) they have participated in, alongside how many certificates they have earned.

Hooks

useQuery

useQuery is a hook provided by the Apollo client and allows you to write custom GraphQL queries and mutations. Below is a code example of a component which utilizes useQuery to fetch the name of a company to welcome the user:

import React from 'react';
import { gql, useQuery } from '@apollo/client';
export { Welcome };

function Welcome() {
  const query = gql`
  query CompanyDetailsQuery {
    CompanyDetails {
      __typename
      name
    }
  }`;

  const { data } = useQuery(query);
  const companyName = data && data.CompanyDetails ? data.CompanyDetails.name : 'No name';

  return (
    <div>
      <h1>Welcome to {companyName}! </h1>
    </div>
  );
}
useCatalogQuery

The useCatalogQuery hook the CatalogQuery GraphQL document, essentially an array of content items with data such as contentType, description, authors, etc. There are three variable fields you pass in to the useCatalogQuery hook.

  • The query field allows you to filter which content in the catalog you want to see. Input for query follows a similar pattern to writing search queries for the Ti Platform, more of which can be read here.
  • The querySort field controls the order of results
  • The querySignature field is a security field…
// Fetch only Learning Path content types, sorted by when it was last updated

const { data, loading, error } = useCatalogQuery({
    variables: {
      query: "contentType:\"Learning Path\"",
      querySort: "updatedAt:asc"
    }
  });
useCatalogContentQuery

The useCatalogContentQuery hook is very similar to the useCatalogQuery hook.

useContentsQuery

The useContentsQuery hook is another way to fetch content based off of content ids. The useContentsQuery hook takes in an array of ids and returns a QueryContents object.

import React from "react";
import { useContentsQuery } from '@thoughtindustries/content';
export { Page }

function Page() {
  const { data, loading, error } = useContentsQuery({
     variables: {
        ids: ['c2653420-295a-4be0-a415-f1256cf81520', 
              '536434a8-2e69-4117-b21e-dfa72900f4bf']
     }
  });
  console.log(data)
  return(
    <div>
      <h1>Hey</h1>
    </div>
  )
}
useLanguagesQueryQuery

The useLanguagesQueryQuery hook is a hook to see all the languages supported in your Learning Instance. You can add different languages in your admin under translations. The useLanguagesQueryQuery takes in no arguments and returns a Languages object.

useAddResourceToQueueMutation

The useAddResourceToQueueMutation hook is relates to enrolling a user in a piece of content. It’s most common use case is in conjunction with the featured-content component.

function Page() {
  const [addResourceToQueue] = useAddResourceToQueueMutation();
  const handleAddedToQueue = (item: FeaturedContentContentItem): Promise<boolean | void> =>
    item.displayCourse
      ? addResourceToQueue({ variables: { resourceId: item.displayCourse } }).then()
      : Promise.resolve(undefined);

  return (
    <div className="px-4">
      <FeaturedContent>
        <ContentTileStandardLayout
          desktopColumnCount={4}
          onAddedToQueue={handleAddedToQueue}
        >
           {/* Content Tile Items */}
        </ContentTileStandardLayout>
      </FeaturedContent>
    </div>
  );
}
useUserStatsQuery

The useUserStatsQuery hook returns a CurrentUser object and takes in no arguments. The CurrentUser object is populated with the fields of availableCoursesCount, certificatesCount, collaborationsCount, completedCoursesCount, and startedCoursesCount.

useRecentContentQuery

The useRecentContentQuery hook relates to fetching content that a user recently viewed. It returns a UserRecentContent object.

Developer and Designer Tools

Storybook Component Documentation

All of our components are visually documented with Storybook. Storybook is a development environment for UI components. It allows you to browse a component library, view the different states of each component, and interactively develop and test components.

Helium Storybook Documentation

Useful Extensions

We have written a lot of code as we’ve built out Helium. Here are a few extensions we found useful if you’d like to try them out too (no affiliation):

GraphQL Explorer

  • Run npm run dev and then go to http://localhost:3000/graphiql to find the GraphQL Explorer
  • Documentation is available when you click docs button 
  • The GraphQL Explorer is an extremely useful tool for writing, testing, and validating GraphQL queries before you use them in your actual code
  • Alternatively, you can view the static version of our GraphQL documentation here.

Figma Helium Design Library

https://www.figma.com/community/file/1157340590339615267

Best Practices

Packages and Dependencies

Because Cloudflare Workers do not utilize containerized processes running an instance of a language runtime, certain Nodejs-specific dependencies – such as fs or crypto – may not function correctly, or could prevent the project from publishing to the Cloudflare Worker correctly. But, any package that utilizes webpack or another polyfill bundler should run within the environment.

Deployment Tips

Add both your sandbox instance and your production instance when running $ npm run authenticate. When you want to deploy your project to production, first deploy to your sandbox instance to preview the changes and then deploy to your production instance when you confirm the look and feel. If you didn’t add both instances when you ran $ npm run authenticate you can simply go to you ti-config.json file and enter the instance details there.

You should deploy early and often to your sandbox. Building a headless LMS and integrating with 3rd parties can introduce complexities if not done carefully.

3rd Party Libraries

Because Helium is fully open source and headless, you can easily use 3rd party developed libraries and components to quickly build a world class learning experience. However, there are some things to be careful about. Helium utilizes Cloudflare workers for hosting and not all components or libraries are compatible with the Helium stack. The quickest way to check is to install a 3rd party library and see if can still deploy successfully to your sandbox.

Writing GraphQL Queries

Only query the data you need, when you need it. One of the greatest features of GraphQL is that you only receive the data you need, nothing more and nothing less. Each component should only fetch data they need. While it is possible to run a single large query in the root component and then pass data to its child components, this can lead to poorer performance.

Was this helpful?

Backdrop for Craig Weiss Group quote

Get the latest developer updates

Thought Industries values your privacy. To learn more, visit our Privacy Statement.