Assignment 2: Anchors & Links

Released: October 5th, 9:00am ET

Due: October 17th, 11:59pm ET

Introduction

Now that we have finished making and preparing the dough for our donuts, it’s time to bake our dough 🧑‍🍳! In this assignment, you will be building upon the concepts introduced in Lab 2 and provide anchor and linking support to a standard node file system. At the end, you will have built a full-stack application that will allow users to create, delete and navigate with Hypertext links via the frontend. You will be graded on the functionality of your frontend and the backend via a test suite. We also want to re-emphasize the 30-minute rule – if you can’t get something that appears straightforward to work in 30 minutes, it may not be your problem. Don’t be afraid to send a message in Slack if something unexpected comes up.

We have refactored our code base to support additional features for this assignment. Please build your system with the new stencil code we provided in this new GitHub Classroom Assignment. Accept the GitHub repository here.

This assignment places a heavy emphasis on the frontend, and contains heavier workload than Assignment 1: Nodes. Start early, start today, start yesterday!

Checklist

Backend:

Frontend:

Deployment:

Architecture

The following architecture diagram should give you a better understanding of the new structure of our full-stack application. A few important things to note:

In your next technical interview, you can refer to this project as a Monolithic Front End (a.k.a a single, large component) that interacts with a microservice (a.k.a. multiple, smaller components) backend. For more information on the tradeoffs between monolithic applications and microservices take a look at this article.

Tips and essential shortcuts for large codebases

Typescript tips

TypeScript/JavaScript has powerful operators that can make a programmer’s life easier. Here are some tips for making your code cleaner!

  1. Logical AND (&&) operator

donut && console.log('delicious')

is the same thing as

if (donut) { console.log('delicious') }
  1. Nullish coalescing operator (??)

It returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand. When used properly, it ensures that the variable we are assigning value to is never undefined or null.

// variable left-hand right-hand const anchorId: string = currentValue ?? 'default string'
  1. Conditional (ternary) operator

isDonut ? 'coffee' : 'milk' is the same as writing

if (isDonut){
    return 'coffee'
} else {
    return 'milk'
}

Additionally click here if the example does not suffice.

  1. === vs. ==

Note: Gains in performance between these two operations is negligable.

  1. Optional Chaining

Introduced in 2020, the optional chaining operator (?.) enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.

In the following example we need to first check for donut.frosting before we can access the flavor of the frosting.

let nestedProp = donut.frosting && donut.frosting.flavor

However, with optional chaining you don’t have to explicitly test and short-circuit based on the state of donut.frosting before trying to access donut.frosting.flavor:

let nestedProp = donut.frosting?.flavor

Essential VS Code shortcuts

  1. cmd + left click: navigate to implementation/usage
  2. cmd + P: search by filename
  3. cmd + shift + P: VS Code commands
  4. cmd + F: search current file
  5. cmd + shift + F: search entire code base

Note, these commands are for Mac. Please replace all cmd with ctrl for Windows.

Demo

You can find a demo of the MyHypermedia application here.

Note: It is entirely possible that there will be some unexpected behavior in this application as it is not set up to handle concurrency properly. This means that the application might behave weirdly if multiple users are interacting with the demo at the same time.

If there is anything that is unclear in this assignment, or you find a bug in our code base, please let us know in Slack!

Known bugs in TA implementation
Here is a list of known bugs in our implementation. You are not expected to have these bugs fixed in your system. We will update this list if more bugs are found.

Backend

Continuing from the lab, you will be implementing your own MongoDB queries.

The backend portion is worth 30% of the assignment grade.

Don’t forget to yarn install and add your .env file!

To Dos

Your primary objective for the backend assignment is to fill in the stencil code found at LinkCollectionConnection.ts and AnchorCollectionConnection.ts. We hope that writing these files will get you more familiar with creating your own MongoDB queries.

Tips:

  1. We highly recommend looking at NodeCollectionConnection.ts as a close reference for these files.
  2. If you are ever confused about how we have set up the MongoDB environment, please look at the Lab 2 handout for an explanation.

Running Tests

Once finished, you should be able to pass all unit (mock) and end-to-end (e2e) tests for Links and Anchors. Note that your Nodes tests should still all pass.

Jest Commands:
Here are the following commands to run tests on a select group of Jest files:

The tests are not setup with Gradescope, they are setup with GitHub Classroom. If all tests pass, there will be a green checkmark in front of your commits.

Custom Methods and Endpoints

Feel free to add your own custom methods in any of the <x>collectionConnections, gateways or router files. The stencil only provides methods that we the TA’s need to power our frontend. But if you think that your frontend implementation would benefit from another endpoint or support from the backend, feel free to add your custom code!

Frontend

The frontend portion is worth 60% of the assignment grade.

Introduction

In the last assignment you handled passing in props to the TextContent and ImageContent components so that they would render the content. You also implemented the NodeBreadcrumb component. The changes that you made were specific to single components rather than the overall system. When implementing links and anchors we need different components to interact with each other, so in this assignment you will get more familiar with the frontend system as a whole.

Frontend Roadmap

Gateway

Read and understand the implementation of the frontend LinkGateway, AnchorGateway and lastly NodeGateway. You will be using methods from these files in the rest of the assigment. Through these methods we are able to make HTTP requests to the backend. You are also implementing NodeGateway.deleteNode

Links and anchors

Folder Structure

Remember, you can search for a file in VSCode using cmd + P (Mac) or ctrl + P (Windows). These commands are very useful to navigate between files, which you will have to do in this assignment!

Gateways

Now that we are supporting Links and Anchors, our frontend gateways - AnchorGateway.ts, LinkGateway.ts, NodeGateway.ts - have gotten more complex. In short look at the following methods and implement them:

Orphan anchors: Anchors that don’t have any links attached to them

Frontend gateway interacting with backend microservices
We have designed this project such that each of the backend microservices (router, gateway, collectionConnection) do not interact with one another. This means that there is absolutely no cross talk between the link, anchor and node backends.

So how do we delete nodes if the deletion of a node relies on the deletion of anchors and links as well? For the purposes of our assignmments (you can do this differently in your final project) we will be using the frontend gateways to handle the cross talk. For example, this is what happens in the frontend’s NodeGateway.deleteNode() method:

  1. Make request to AnchorRouter to get anchors by nodeId
  2. If step 1 was successful, make request to LinkRouter to get links by anchorIds
  3. If step 2 was successful, call frontend LinkGateway.deleteLinks() to delete links and orphan anchors
  4. If step 3 was successful, make request to NodeRouter to delete node

You can make calls to other gateways by importing them, for example in LinkGateway, we already import AnchorGateway so that we can make a call to an AnchorGateway and delete the orphanAnchors. You will want to do this when you are implementing NodeGateway.deleteNode()!

import { AnchorGateway } from '../anchors'
await AnchorGateway.deleteAnchors(orphanAnchors)

TODO: Implement NodeGateway.deleteNode()

In Lab 2 you handled selecting an extent on the node that you are currently on. Once you handled the case of selecting the extent, you used setSelectedExtent to set a app state variable that was passed down from MainView so it can be accessible to any component in your app. There are a few other state variables that are essential when implementing Start Link and Complete Link. They are as follows:

App States

const [isLinking, setIsLinking] = useState(false)
const [startAnchor, setStartAnchor] = useState<IAnchor | null>(null)
const [endAnchor, setEndAnchor] = useState<IAnchor | null>(null)

Anchor States

Note: To modify an array state variable you should use an updater function to prevent race conditions.

setArrayState(prevArray => ({
  arrayFoo: [...prevArray.arrayFoo, newElement]
}))
const [selectedAnchors, setSelectedAnchors] = useState<IAnchor[]>([])
const [selectedExtent, setSelectedExtent] = useState<Extent | null>(null)

Note: If the Extent is null then it should link the entire node. If it is undefined then it should return an error.

In Lab 2, we setSelectedExtent in ImageContent.tsx and FolderContent.tsx respectively. Since the setSelectedExtent method was passed down from MainView.tsx, we are able to access them from MainView.tsx anywhere in our codebase!

TODO:

Functionality Expectations

  1. You should be able to start a link from the currently selected extent of any node to go into linking mode - which should be clearly indicated.
    • There are no TODOs which indicate where exactly this mode should appear - we leave that up to you!
    • The linking mode should have a way to cancel / escape the linking mode.
  2. When you are in linking mode, you should be able to complete the link based on the currently selected extent. This should open up a CompleteLinkModal.
  3. Design the CompleteLinkModal. If you want you can also change the ILink interface in Types > ILink and add additional properties to be added to the database! Title and Explainer are expected to be part of this modal.
  4. Make requests via the gateway so that the Anchors and Links are both succesfully created and in the database.

NodeLinkMenu

Now that we have the ability to create links between any two Extents on two documents (or within the same document). Your next task is to completely design the NodeLinkMenu, which is where you render the links for a particular node, and handle the user interactions with that link list!

TODO: Design and implement your NodeLinkMenu. You can find this in src > components > NodeView > NodeLinkMenu

The NodeLinkMenu in the demo is only one possible design for this menu. Feel free to relocate it, adjust it, and make it your own, as long as it is usable, well designed, and the following three expectations are clear.

Click Events

You will want to use onClick when you are designing the NodeLinkMenu component. The following code snippet is an example of how that could look.

const clickDonut = (e: React.MouseEvent, flavor: IFlavor) => { switch(e.detail){ case 1: console.log('Left click', flavor.sprinkles) case 2: console.log('Right click', flavor.dough) } } return ( <div className="donut" onClick={(e) => clickDonut(e, flavor)}> </div> )

Note that e.detail is how you can specify the difference between left click and right click. The above method would print the type of sprinkles on left click, and the dough on right click.

Don’t forget to set the css cursor property. This is an important thing to set when it comes to usability with onClick elements!

Functionality Expectations

  1. You should be able to select an item in this menu, which selects the anchors in the currentNode. Think about how we can use the setSelectedAnchors state variable to do this! But how do we get the anchors on either end of the link? Look at the ILink interface and REMEMBER, we can always make calls to the AnchorGateway.
  2. You should be able to follow a link, this means that you should fetch the respective node on the other end of the link and navigates to that node
  3. You should be able to delete a link (and delete both of the link’s anchors). You should make gateway calls to the AnchorGateway and LinkGateway to do this!

This component should be well designed and usable. We recommend creating a mockup in Adobe Xd or Figma, feel free to check in with a TA to ask for feedback on your design!

Deployment

Deployment is worth 10% of the assignment grade.

You have done a great job if you are reaching this step of the assignment! After you completed coding your system, it is time to deploy it to remote servers so everyone with internet can access it. To introduce you to different technologies, we are going to deploy backend to Heroku, and frontend to Google Firebase. Feel free to share the deployment link (not the code) with family and friends, or you may even want to showcase it on your resume!

Deploying Backend

First, we’ll want to deploy our backend service to Heroku. The first step is to register an account and install Heroku CLI. To do that, please follow the link here: Getting Started on Heroku with NodeJS.

Setting up the environment

Now you should have the Heroku environment ready to go. We can start deploying our backend code. Since we have our server and client in one repo, it’s crucial that we only deploy a subfolder of the whole repository.

Creating instance and deploying

We have deployed our code to Heroku! But now it will not work yet, because we have not configured environment variables and our application does not know what port to start on.

Configuring environment variables

Config

Now, if you refresh your backend instance, you should see that “MyHypermedia Backend Service” is rendered on the DOM. Moving on to deploying frontend!

Commonly Asked Questions
Q: What if I already have a Heroku account? Do I need to create a new one for this course?
A: No, you can use your personal account for Heroku. If you run out of your instance limit, consider registering a new account.

Q: The app works locally but Heroku says “Application Error”. What’s wrong?
A: Go to Heroku dashboard and select your instance. Click More button and select View logs option. You will see the console output and debug from there.

Check whether you have configured environmental variables (config vars) correctly.

Note that Heroku does not install devDependencies if you have used your own npm package; consider moving it to dependencies in package.json.

TODO: Put your backend Heroku URL in the assignment README.md

Deploying Frontend

After deploying backend, we will be deploying our frontend using Firebase, a Google service. Before we start, we need to change the endpoint in the frontend codebase to point to the backend service we just deployed. Let’s do it!

Where can I find my backend deployment link?
Click the “Open App” button in Heroku instance page, your backend app will open in a new window.

The first step is to change the endpoint in Frontend to point to the remote backend service.

Changing the endpoint

The second step is to install the Firebase CLI.

Installing Firebase CLI

Follow the instruction here to install Firebase CLI.

Next, let’s log into the Google Firebase using your personal Google Account.

The account you use must not be affliated with Brown University. Use your personal Google Account, or register a new one. Brown University accounts does not allow users to create Firebase apps.

After we log in, it is time to create and configure the Firebase app!

Create and configure Firebase app

Now we have created a Firebase instance on the cloud! Since we are deploying our React application, we will use the production version of React. How do we do that?

Build production React and deploy

All done, have fun! If everything runs smoothly, you should be seeing your app deployed on Firebase. Firebase would give you the URL of the app upon successful deployment.

You just deployed a fully-fledged hypertext system. Open your frontend deployment, and test it as a user!

TODO: Put your frontend Firebase URL in the assignment README.md

Extra Credit

This extra credit is not a capstone or graduate student requirement.

Now that you have implemented anchors and links for basic image and text nodes, it is the time to try to implement your own node types for extra credit. These can be anything ranging from a PDFNode, SpreadsheetNode, AudioNode, VideoNode or any other custom node type you want to add. For rendering audio, PDFs, feel free to use any packages of your choice!

These expectations are very broad - feel free to reach out to a TA with any questions you have about the specifics of how to implement one of these node types - a great place to start is thinking about what Node modules may be useful…

Creating the new node type/view counts as 10 pts and adding linking (and anchor) functionality to this view adds an extra 10 pts.

Handin

You should submit your project to the Nodes | Assignment 1 assigment in Gradescope via your GitHub repository. You should also have filled out your README.md with the following information:

Note: We do not run tests through Gradescope, we run them in GitHub classroom.

Grading Breakdown

This component should include a rubric, for the backend and the frontend

Backend Tests - 30pts

Task Grade
Implement AnchorCollectionConnection.ts 15 Pts - yarn unit-test - all tests pass
Implement LinkCollectionConnection.ts 15 Pts - yarn unit-test - all tests pass

Frontend Functionality - 60pts

Task Grade
Implement frontend NodeGateway.deleteNode() should delete node as well as relevant anchors and links 10 pts - TA will verify functionality on deployed version
Linking works as expected 25 pts - TA will verify functionality on deployed version
NodeLinkMenu meets expected functionality 25 pts - TA will verify

Rubric specifications for Linking and NodeLinkMenu:

Sub-rubric Criterion
Methods implementation 10 pts - correctly implemented
5 pts - partially correct
0 pts - not implemented
Design and usability 10 pts - clicks behave as expected, items are usable and operate as expected
5 pts - clicks are not clear, poorly designed and not easily accessible
0 pts - no design consideration, buttons are not functional
Overall design and usability 5 pts - TA will verify

Deployment - 10pts

Task Grade
Deploy backend 5 Pts - TA will verify
Deploy frontend 5 Pts - TA will verify

Total = 100 pts

Extra Credit - 20pts

Task Grade
Support an additional node type in your MyHypermedia app 10 Pts - TA will verify
Add linking and anchoring functionality for the additional node type that you support 10 Pts - TA will verify