Try   HackMD

Assignment 2: Anchors & Links

Released: September 28th, 2023
Due: October 11th, 2023 at 11:59 PM ET

⚠️ Do not clone the Assignment 2 stencil code until you have submitted Assignment 1 for the last time to Gradescope. We will be checking to make sure you do not submit Assignment 1 after cloning Assignment 2.

Introduction

Now that we have finished making and preparing the dough for our donuts, it's time to cook them 🧑‍🍳! In this assignment, you will be building upon the concepts introduced in Lab 2 to add 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 backend via a test suite and the functionality of your frontend manually.

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 or go to TA hours if something unexpected comes up.

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

Note: Assignment 2 builds on the code from Lab 2; make sure to complete Lab 2 before starting.

Checklist

Backend:

  • Implement AnchorCollectionConnection and LinkCollectionConnection

Frontend:

  • Implement FrontendNodeGateway.deleteNode()
  • Load anchors from the database instead of randomly generating anchors in TextContent.tsx and ImageContent.tsx
  • Implement handleStartLinkClick and handleCompleteLinkClick in NodeView.tsx
  • Design the CompleteLinkModal
  • Design and implement the NodeLinkMenu

Deployment:

  • Deploy the backend to Render
  • Deploy the frontend to Vercel

Graduate/Capstone Requirement:

  • Implement additional node type
  • Add anchoring and linking functionality for new node type

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:

  • Additional functionality is added to our application through more backend services that do not depend on each other.
  • Our frontend is a single application that consumes all of our backend services.
  • Although all of our MongoDB collections belong to the same cluster, each collection is entirely independent of the other collections in the cluster.

Note: In your next technical interview, you can refer to this project as having a monolithic frontend (AKA a single, large component) that interacts with a microservice (AKA multiple, smaller components that each perform a particular function) backend. For more information on the tradeoffs between monolithic applications and microservices, we encourage you to 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 (x ? y : z)

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

if (isDonut){
    return 'coffee'
} else {
    return 'milk'
}
  1. === vs. ==

  • == converts the variable values to the same type before performing comparison. This is called type coercion.
  • === does not do any type conversion (coercion) and returns true only if both values AND types are identical for the two variables being compared.

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

  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 VSCode shortcuts

  • cmd + shift + P: VSCode commands
  • cmd + left click: navigate to implementation/usage
  • cmd + P: search by filename
  • cmd + F: search current file for term
  • cmd + shift + F: search entire code base for term
  • cmd + shift + L: select all instances of selected term in the current file
  • option + left click: add additional cursor at left click location
  • option + ↓ or option + ↑: move line of code up or down

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

Demo

Note: The demo may take up to 30 seconds to load due to the free backend we used for hosting. Additionally, you might notice some strange behavior if there are multiple students interacting with the demo at the same time.

You can find a demo of the MyHypermedia application here.

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 npm install and add your .env file!

To Dos

Your objective for the backend assignment is to fill in the stencil code found at AnchorCollectionConnection.ts andLinkCollectionConnection.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 the Node version of these files as a close reference for the Anchor/Link versions (e.g. nodeCollectionConnection.ts)
  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:

  • Running a single file: npm test <file-name>
    • e.g. npm test findAnchorById.spec.ts will run findAnchorById.spec.ts
    • You can remove the file type suffix, so npm test findanchorbyid works too - it is case insensitive
  • Running a folder: npm test <folder-name>
    • e.g. npm test Anchors/Mock/AnchorCollectionConnection
    • e.g. npm test Anchors will run all tests found in Anchors
    • e.g. npm test e2e will run all e2e unit tests

Custom Methods

Feel free to add your own custom methods in any of the <x>CollectionConnections, gateways, or router files, as long as the test suite still passes.

The stencil only provides methods the TAs 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

  • Make sure that you've completed Lab 2. It's important to understand extents before you begin this assignment.
  • Read and understand the implementation ofFrontendLinkGateway, FrontendAnchorGateway, and FrontendNodeGateway.
    • You will be using methods from these files in the rest of the assigment.
  • Implement FrontendNodeGateway.deleteNode
  • Load anchors from the database instead of randomly generating anchors in TextContent.tsx and ImageContent.tsx
  • Implement the ability to Start Link
  • Implement the ability to Complete Link and design the CompleteLinkModal
  • Design your NodeLinkMenu. Ensure the menu is always up-to-date.

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 have gotten more complex. FrontendNodeGateway.deleteNode() now has to delete all links and anchors associated with the node before deleting the node itself.

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 FrontendNodeGateway.deleteNode() method:

  1. Make request to get anchors by nodeId via FrontendAnchorGateway
  2. If step 1 was successful, make request to get links by anchorIds via FrontendLinkGateway
  3. If step 2 was successful, call frontend FrontendLinkGateway.deleteLinks() to delete links and orphan anchors
  4. If step 3 was successful, make the request to delete node via FrontendNodeGateway

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

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

TODO: Implement FrontendNodeGateway.deleteNode()

Loading Anchors

During the lab, the functions generateRandomTextAnchors and generateRandomImageAnchors were used to randomly generate anchors to display. Now that we have a working backend, we can instead load and display the node's anchors from the database using FrontendAnchorGateway.

TODO: Load anchors from the database for a given node instead of randomly generating anchors in TextContent.tsx and ImageContent.tsx

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. There are a few other global state variables that are essential when implementing Start Link and Complete Link.

To use a global state variable, import the atom (e.g. isLinkingState) from global/Atoms and import useRecoilState from recoil.

App States

  • isLinking: boolean that indicates whether we are linking from a node.
const [isLinking, setIsLinking] = useRecoilState(isLinkingState)
  • startAnchor: IAnchor that indicates the particular anchor that we are linking from.
  • endAnchor: Indicates the anchor that we are linking to. This is important when we want to complete the link.
const [startAnchor, setStartAnchor] = useRecoilState(startAnchorState)
const [endAnchor, setEndAnchor] = useRecoilState(endAnchorState)

Anchor States

  • selectedAnchors: Indicates the list of anchors that are currently selected.
const [selectedAnchors, setSelectedAnchors] = useRecoilState(selectedAnchorsState)
  • selectedExtent
const [selectedExtent, setSelectedExtent] = useRecoilState(selectedExtentState)

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. SinceselectedExtentState is an atom in Recoil, we can access and mutate the state from anywhere in our codebase!

TODO:

  • Implement handleStartLinkClick and handleCompleteLinkClick in NodeView.tsx and ensure that the functionality meets the expectations defined below.
  • Design the CompleteLinkModal

Functionality Expectations

  1. You should be able to start a link from the currently selected extent of any node to go into linking mode.
  2. It should be clearly indicated when the user is in linking mode.
    • There are no TODOs which indicate where or how to indicate this mode - we leave that up to you.
  3. There should be a way to cancel / escape linking mode.
    • Again, it's up to you how you'd like to implement this.
  4. 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.
  5. Design the CompleteLinkModal. From this modal, the user should be able to set the link Title and Explainer.
    • If you want, you can also change the ILink interface in Types > ILink and add additional properties to be added to the database. Make sure to change the interface in both the server and client folders if you do this.
  6. Make requests via the frontend gateways so that the anchors and links are succesfully created and in the database.

NodeLinkMenu

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

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

NOTE: See the #fixes channel on Slack for a util class that you can put in this folder!

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 functionality expectations described below are satisfied.

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 FrontendAnchorGateway.
  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 links and delete anchors. (You should have a "Delete Link" button and a "Delete Anchor" button.) 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 on paper or in Adobe Xd or Figma. Feel free to check in with a TA to ask for feedback on your design!

Helper functions you may find useful

  • loadAnchorToLinksMap: returns a map from an anchorID to the anchor and its links
  • fetchNodeFromLink: returns the nodeId to navigate to for a given link and set of properties
  • router.push(nodeId): navigates to a given nodeId (requires that the const router = useRouter() hook is somewhere in the same file)
  • includesAnchorId: returns a boolean representing whether an anchorID is in the selected anchors

Deployment

Deployment is worth 10% of the assignment grade.

The deployment steps are the same as for Assignment 1. You can find the link to the deployment instructions here.

Reminders before deploying:

  • Don't forget to remove console.log() debug statements
  • Remember to replace the prodEndpoint in endpoint.ts with your production endpoint URL (include the trailing slash!)
  • Remember to make your project names and deployment URLs anonymous (do not include your name/username)

Graduate/Capstone Requirement

These features are required for graduate-level and capstone students. For students who are not capstone or 2000-level, you can complete these features for a maximum of 15 extra credit points.

Now that you have implemented anchors and links for basic image and text nodes, it's time to implement your own node types. 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 package(s) of your choice!

If you are taking this course for 2000-level/capstone credit, you are expected to create a new node type/view and add linking/anchor functionality to the view.

If you are not taking this course for 2000-level/capstone credit, you can add a new node type/view and add linking/anchor functionality to the view for extra credit.

You should briefly explain your added functionality in your README.md so we know where to look for it.

Handin

You should fill out your README.md with the following information:

  • Notable design choices
    • Including any packages that you used.
  • Frontend Vercel URL
  • Backend Render URL
  • Known bugs
    • Only report bugs related to what you have implemented. We will be more lenient with grading if bugs are reported!
  • Time taken
  • Master's/Capstone Requirement or Extra Credit (if applicable)

After completing your README, you should submit your GitHub repo to the Gradescope assignment.

Please ensure your entire project is anonymous and your name is not present anywhere (including in the deploy URLs), since we use anonymous grading.

Grading Breakdown

For students taking this course as a capstone or graduate level course, the assignment is out of 115 points. For students not taking this course as a capstone or graduate level course, the assignment is out of 100 points.

Linting - 5 Pts (autograded)

Task Grade
No ESLint or Prettier warnings 5 Pts

Run npm run lint on the server/ and client/ directories to run the linter locally and fix any issues before you submit!

Backend Tests - 25 Pts (autograded)

Task Grade
Implement AnchorCollectionConnection.ts and LinkCollectionConnection.ts 25 Pts

Run npm test on the server/ directroy to run the test suite locally and fix any issues before you submit!

Frontend Functionality - 60 Pts (TA will grade)

Task Grade
Implement frontend NodeGateway.deleteNode() should delete node as well as relevant anchors and links 10 pts - TA will verify
Linking works as expected 25 pts - TA will verify
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 (TA will grade)

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

Total = 100 pts

Graduate/Captone Requirement - 15pts

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