Coder Social home page Coder Social logo

inclass's People

Contributors

gabeabrams avatar lbjay avatar shihanli avatar stesta03 avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

harvard-edtech

inclass's Issues

Build Credential and XML Reader

This update to server/Store will add reading of each app's install.xml and credentials.json file to the loading process.

Add the contents of install.xml to a new field in the app: app.installXML.

Add the contents of credentials.json to a new field in the app: app.installationCredentials.

If either of these files does not exist, throw an error with the message 'The app "${appId}" in catalog "${catalogId}" could not be loaded because its credentials.json file does not exist' or 'The app "${appId}" in catalog "${catalogId}" could not be loaded because its install.xml file does not exist'

If the credentials file cannot be parsed as JSON, throw an error: 'The app "${appId}" in catalog "${catalogId}" could not be loaded because its credentials.json file could not be parsed'

Serve Logo and Icons in Store

You'll be editing server/Store/index.js

You're going to be doing two things:

First, for each app, now also serve the app's icon just by calling the helper you wrote (server/Store/helpers/serveIcon.js) with the appropriate args

Second, serve the store logo by calling your helper (server/Store/helpers/serveStoreLogo.js) with this.storeMetadata.logoFullPath as the argument.

Make AppItem clickable

Add a prop called onClick and if it is defined (this is an optional prop with default null), then add an onClick to the whole div.

Also, turn the AppTitle into a button, and also pass the onClick into the title. If there is an onClick, then add the onClick to the button. If there is no onClick, then set the button's pointerEvents: false so that the button is non-interactable.

Build Catalog Detector

You'll be editing server/detectCatalogAndPermissions.js

Before getting started, take a look at the following: docs/types/Catalog for a description of the Catalog object, visit bit.ly/caccl-api for the definition of the api object (specifically check out api.course.get and api.account.get).

In Canvas, there are "accounts." You can think of an account as a set of courses. Thus, each course belongs to a single account. Notice that each Catalog has a list of accounts that it applies too. If you launch the app store from a course within any of those accounts, then the user will be shown that Catalog.

You have two goals in this task. The first goal is to figure out which catalog the user should be shown (find the catalogId). The process you'll probably want to follow is:

  1. Use await api.course.get({ courseId: <put course id here> }) to get info on the course
  2. Extract the course's account id from the response
  3. Go through all the catalogs to figure out which catalog has this account id in its list
  4. When you find one that matches, save that catalogId for later

Your second goal is to determine if this user is an admin for this catalog. The user is an admin if they are able to get info on one of the accounts listed in the catalog. In other words, if api.account.get returns something and doesn't result in an error for one of the accounts in the catalog, that person is an admin. To be even clearer, one by one, go through each account in the catalog. If we're able to get info on one of these accounts (no error), then the person is an admin. Start with the account that the course is in. If that fails, continue with the rest of the accounts in the catalog (any order is okay as long as you don't check the account that the course is in twice).

Finally, return an object { catalogId, isAdmin }.

Get Catalog Data Endpoint

This is an endpoint on the server that's exposed to the client. This is not designed to be a public endpoint (e.g. /about or /contact-us). Instead, this is just for requesting data from the server.

You will be editing /routes and implementing the /catalog route.

Add the route app.get('/catalog', (req, res) => { ... });

The route will call store.getCatalogAndPermissions(api, launchInfo) where api is req.api and launchInfo is req.session.launchInfo. If either of these objects does not exist, respond with an error: 'We could not load your customized list of apps because we couldn't connect to Canvas and process your launch info. Please re-launch. If this error occurs again, contact an admin.'

This endpoint will respond with the following object:

{
  success: true/false,
  message: <error message if success is false>,
  catalog: <catalog metadata if success is true>,
  isAdmin: <included if success is true>,
}

If the error has a .code field, you can use the message of the error err.message. Otherwise, print the error to the console.log and return a generic "An unknown error occurred while getting the list of apps in the current catalog. Please contact an admin." message.

Load App Icons

Now, also load app icons. Each app will have an icon either named icon.png or icon.jpg. Use fileExists to see which one exists and just use the one that's there.

If neither file exists AND we aren't extending a parent, throw an error.

Add this to the App as app.icon in the form:

app.icon = {
  fullPath: <the full path to the icon>,
};

Implement Store Handler

Your job is to implement the Store class in server/Store/index.js. This class's job is to handle all of the loading and logic around catalogs and metadata.

The Store class will have a few methods:

_attemptLoad()

Function that attempts to perform a load. If successful, swaps out our metadata objects. If failed, leaves current metadata objects as is.

  1. When run, load all the metadata for the store, catalogs, and apps (using loadStore.js)
  2. Once everything is loaded, pre-process the data into four data structures:

storeMetadata – the store metadata object

installData – a mapping { catalogId => appId => { installXML, installationCredentials } }

accountIdToCatalogId – a mapping { accountId => catalogId } taken straight from each catalog's accounts field

catalogIdToCatalogMetadata – a mapping { catalogId => catalog object } where all apps in catalog.apps have their installXML and installationCredentials removed

If all of this goes successfully, serve the screenshots then swap out this.storeMetadata, this.installData, this.accountIdToCatalogId, and catalogIdToCatalogMetadata.

To serve the screenshots, use the helpers/serveScreenshots.js function to serve each app's screenshots (one by one). You'll need to pass in this.expressApp in addition to the relevant ids and app metadata object. This helper function gives you a new app metadata object. Make sure you replace your current copy of the app metadata with the one returned from this helper.

getCatalogAndPermissions(api, launchInfo)

A function that simply calls server/helpers/detectCatalogAndPermissions with the given arguments but also adds this.catalogIdToCatalogMetadata as a third argument. Then, it uses the returned catalogId field to look up the catalog metadata object and returns the following object:

{
  catalog: <catalog metadata object>,
  isAdmin: <same boolean we got from the helper>,
}

getInstallData(catalogId, appId)

A function that looks up the install data, adds in some app metadata, and returns it. Throw an intuitive error if the catalog or app does not have installData. This is the form of the object you'll be returning:

{
  name: <title from app metadata>,
  description: <subtitle from app metadata (NOT APP DESCRIPTION)>,
  key: <consumer_key from installationCredentials inside installData>,
  secret: <consumer_secret from installationCredentials inside installData>,
  xml: <installXML from installData>,
  launchPrivacy: <launchPrivacy from app metadata>,
}

getStoreMetadata()

A function that returns the store metadata.

Get Store Data Endpoint

This is an endpoint on the server that's exposed to the client. This is not designed to be a public endpoint (e.g. /about or /contact-us). Instead, this is just for requesting data from the server.

You will be editing /routes and implementing /store, which returns the store metadata.

Add the route app.get('/store', (req, res) => { ... });

To do this, you'll use the store's getStoreMetadata function.

Send the user the following object using res.json(...):

return res.json({
  success: true/false,
  message: <error message>,
  store: <store metadata>,
});

How do we know if there's an error (no success)? Put everything before it in a try/catch and if an error occurs, you know. Also, if store metadata is undefined/null, success is false and message is "The app catalog is not ready yet. Please check back in a few minutes. If this error continues to occur, contact an admin."

If the error has a .code field, you can use the message of the error err.message. Otherwise, print the error to the console.log and return a generic "An unknown error occurred while getting the store metadata. Please contact an admin." message.

Live Reloaded Store

You will be editing server/Store/helpers/callOnSchedule.js

Big picture goal: every x seconds, attempt to reload the store. If an error occurs, revert back to the previous version of the store.

You will need to get a library that allows you to schedule tasks every minute.

App Lifecycle Integration Test

You'll be editing: test/selenium/integration/server/appLifecycle.js

Find docs on the driver here: bit.ly/dce-selenium

Before running the tests, you need to start the server. Here's how:

Open two tabs, and in the first one, run npm run dev:canvas. In the second, run npm run dev:server-installable.

Now we can run tests.

To run the tests, in a third tab, use npm run selenium.

  1. Install the app
  2. List the apps and make sure the app is installed
  3. Uninstall the app
  4. List the apps and make sure the app is not installed

Catalog Integration Test

You'll be editing an integration test file: /test/selenium/integration/server/catalogEndpoint.js

See comments in that file for more details. Also see dce-selenium docs for help: bit.ly/dce-selenium

Before running the tests, start the server to test:

Open two tabs, in the first tab run npm run dev:canvas and in the second tab, run npm run dev:server-installable

To run the tests, use:

npm run selenium

AppPage - Turn state into Props

Add isInstalled as a prop and add onSupportClicked and onInstallClicked and onUninstallClicked as props, call those functions when the buttons are clicked.

Uninstall App Endpoint

You'll be editing server/routes.js to add a DELETE /uninstall endpoint.

Given the app's lti ids (get them using req.body.ltiIds), use await req.course.app.remove({ ... }) to uninstall each app then send a response: { success: true/false, message: error message if success is false }. Use return res.json(...) to send the response.

If the error we catch has a err.code field, send err.message to the user. If not, tell the user: An unknown error occurred while attempting to uninstall an app. Please contact an admin.

Install App Endpoint

This endpoint will be a simple POST endpoint that installs an app into a course.

You will be editing server/routes.js and will be adding a /install/:appId route.

Use req.params to get the appId from the url: req.params.appId.

When a user visits that route, we get the current catalogId stored in req.session.catalogId, we use store.getInstallData to get the install data, add a new courseId parameter using the value in req.session.launchInfo.courseId. Then, install the app using req.api.course.addApp (see bit.ly/caccl-api for more info).

Then, respond to the client with the following body:

{
  success: true/false,
  message: 'Human-readable message upon failure',
}

If the error has a .code field, you can use the message of the error err.message. Otherwise, print the error to the console.log and return a generic "An unknown error occurred while installing this app. Please contact an admin." message.

Store Metadata Integration Test

You'll be editing /test/selenium/integration/server/storeEndpoint.js

Find docs on the driver here: https://harvard-edtech.github.io/dce-selenium/

Before running the tests, you need to start the server. Here's how:

Open two tabs, and in the first one, run npm run dev:canvas. In the second, run npm run dev:server-installable.

Now we can run tests.

To run the tests, in a third tab, use npm run selenium.

Integrate Hot Store Reloading

This is a pretty simple task. You'll be editing/server/Store/index.js to add hot reloading.

  1. Import the callOnSchedule function
  2. In the constructor of the Store class, create a function:
const hotReload = () => {
  this._attemptLoad();
};
  1. Add a new prop to the STORE_CONSTANTS.js file: hotReloadSecs and set it to 60
  2. Call the hotReload function every STORE_CONSTANTS.hotReloadSecs seconds

The toughest part of this task is testing: you'll need to copy a test store folder, modify it before the hot reload, and check to make sure the changes are reflected, then break the store metadata, make sure it didn't get loaded, then fix it and make sure it did get loaded, set beingEdited: true in the storeMetadata and make some other change, wait for the hot reload and make sure the store didn't get updated (that other change didn't make it into the Store class).

Serve Store Logo

You'll be editing server/Store/serveStoreLogo.js

Your job will be to take the full path of the logo and serve it at /public/logo using the usual techniques

Tags Postprocessor

This helper will post process an entire catalog, performing two key operations: adding a tagColors object to the catalog if it doesn't have one, and adding "other/uncategorized" to apps that don't have any items under a tagName.

This helper will post process an entire catalog. It will:

  1. Build the Catalog object completely in this format:
    tagsToShow: [ { tagName: 'cost', tagColor: 'blue', }, { tagName: 'type', tagColor: 'red', }, ]

  2. If the catalog doesn't have a tagColors object, add one. The keys should be the set of keys from all of the apps. The values should be cyclicly chosen from the following list of colors:

ff2600
008e00
008e00
9437ff
ff2f92
ff9300
d783ff
005392
929000

  1. For every app in the catalog, if the app does not have any values for a given tagName in the tagsToShow object, add ['other/uncategorized'] as its item array.

Load Store Logo

You'll be editing server/Store/helpers/loadStore.js.

See the TODO and slack with questions

Filter Counts Calculator

You will be editing client/src/utils/filter/genTagValueCounts.js.

Count is calculated by:

  1. Find the list of apps that would show up if the other filter categories are left in their current state and the current filter category is all checked

  2. Within this list of filter items in this filter category, for each filter item:
    a. Find the number of apps in the list from step 1 that have the current filter item’s tag
    b. Write than number in the “count” box

Please slack with any questions.

List Installed Apps Endpoint

You will be editing server/routes.js.

This /installed-apps pulls the list of LTIs in a course, matches them with apps in the catalog, and returns a list of installed apps with both their LTI id and their appId.

  1. Get the current catalogId from req.session.catalogId
  2. Look up the current catalog with Store.getCatalog (if no catalog returned (undefined or null), send error: "We could not get your list of currently installed apps because your session has expired. Please re-launch from Canvas. If this issue continues to occur, please contact an admin.")
  3. Get the current courseId from req.session.launchInfo.courseId (surround with try catch, send error: "Please re-launch this app from Canvas to continue. If this continues to occur, please contact an admin")
  4. Get the list of LTI apps installed into the course using await req.api.course.app.list(...)
  5. For each LTI in the list (call it ltiApp), compare the following with the list of apps in the catalog (call it catalogApp) to determine matches:
  • ltiApp.privacy_level should match with catalogApp.launchPrivacy
  • ltiApp.consumer_key should match with the app's key (use store.getInstallData(...))
  • ltiApp.name should match with catalogApp.title
  • ltiApp.url should be inside of the app's xml (use store.getInstallData(...))

Finally, return a list of matches. If an app does not match, do not include it.

Return the following object:

{
   success: <true/false>,
   message: <error message if success is false>,
   apps: [
     {
       ltiIds: array of ids from Canvas,
       appId: the app's app store id,
     },
     ...
   ],
}

Serve App Icons

You will be editing server/Store/helpers/serveIcon.js.

This task will be very similar to the task where we served the screenshots.

Your job will be to take the app's icon (its file path will be at app.icon.fullPath) and serve it at /public/<catalogId>/<appId>/icon and then add this url at app.icon.url.

Finally, edit server/Store/index.js, specifically the _attemptLoad function:

In the loop where you're editing each app, add one more step: call serveIcon with the app and then remember to reassign the app:

const appWithServedIcon = serveIcon(app);

Make App.creator Into Array

If in an app's metadata.json file the creator field is not an array, turn it into a single element array:

"creator": "DCE"

becomes

app.creator = ['DCE']

Build Filter Logic

Implement the following files:

client/src/utils/filter/filterByQuery.js

If query is empty or just whitespace, return all apps

Otherwise, search the following fields. If any match, keep the app:

  • apps[i].title
  • apps[i].subtitle
  • apps[i].description (if it exists)

client/src/utils/filter/filterByTags.js

tags is an object of form:

{
  tagName => {
    color: 'blue',
    tagValues: tagValue => isChecked,
  }
}

For each app: keep it if for each tag in tags, the app has one of the checked itemNames in apps[i].tags[tagName]

client/src/utils/filter/excludeTagName.js

Given a tags object and a tagNameToExclude, remove the tagNameToExclude from the mapping.

By remove, I mean that the key and its value should be deleted. Example:

const tags = {
  cost: ...,
  type ...,
};

If tagNameToExclude is "cost", the resulting tags object returned is:

const tags = {
  type ...,
};

Build Screenshot Serving

Start by refreshing your Express.js knowledge on serving static files.

You'll be editing server/Store/helpers/serveScreenshots.js

The function takes two arguments: an express app and an app metadata object (see docs/types/App for more info).

App object:

Each app object might have an app.screenshots property. If it does not have this property, you have nothing to do. If it does have this property, you'll want to loop through all the screenshots and do the following:

Serve app.screenshots[i].fullPath at the following path: /public/<catalogId>/<appId>/screenshots/<filename>

If the file does not exist, create a pretty error and throw it. Example message "The app '' in catalog '' listed a screenshot with filename '' but that file does not exist"

Add a url property to each screenshot that holds the url above.

Return the updated app.

Reminder: you are not allowed to edit function arguments (you need to reassign the app before updating it)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.