zooniverse / Panoptes-Front-End

Front end for zooniverse/Panoptes
https://www.zooniverse.org
Apache License 2.0
64 stars 75 forks source link
hacktoberfest panoptes-platform zooniverse

Panoptes (front end)

Build Status

Coverage Status

Getting started

We are no longer actively developing features for this app. PRs will be accepted for bug fixes, translations, and content updates. Active feature development is happening at https://github.com/zooniverse/front-end-monorepo/

With Docker

To avoid having to install Node.js or any other dependencies, you can run everything with Docker and Docker Compose.

With Node.js

Make sure you have Node 8 and npm 5 or greater. It's recommended you manage your Node installations with nvm.

⚠️ Note 1: npm ci (clean install) is preferred over npm install, as it doesn't modify the package lock.

⚠️ Note 2: as of Node 16.15, running npm ci results in errors such as npm ERR! ERESOLVE could not resolve and Conflicting peer dependency: foobar@x.y.z You can bypass this problem by instead running npm ci --legacy-peer-deps. Please see issue 6155 for more details.

Viewing the Website

Open your web browser of choice and go to https://localhost:3735/

If you want to login via the Panoptes API and view authenticated pages, then you'll need to set up and use https://local.zooniverse.org:3735 instead of using localhost:3735. Otherwise, you'll run into CORS errors. (You need to add the hostname to your hosts file, pointing to local. Instructions are on our Stackoverflow.)

Troubleshooting: web browser blocks local website

The problem: when attempting to view localhost:3735 or local.zooniverse.org:3735, my web browser stops me and shows a warning screen.

Example errors: "Your connection is not private / NET::ERR_CERT_AUTHORITY_INVALID" on Chrome 104; "Warning: Potential Security Risk Ahead" on Firefox 103; "This connection is not private" on Safari 15.4.

The cause: the local web server is running HTTPS, and it's using a self-signed certificate. Modern web browsers consider these certificates very untrustworthy, and a possible indicator of a man-in-the-middle attack.

The solution(s):

⚠️ Warning: please be careful if you do change your web browser's or computer's security settings.

Configuration

The app can be configured using the following environment variables:

Configuration notes

Development

New GitHub PRs from within the Zooniverse organisation will be staged by Jenkins as part of the CI process. Once CI finishes, your changes should be staged at https://pr-{PR-Number}.pfe-preview.zooniverse.org. Jenkins sometimes times out before finishing the build. If a PR build fails, use the link to Jenkins (from your PR) to log in and try restarting the build.

For testing with production data, you can add env=production to your development url, e.g. localhost:3735/projects?env=production. Note that it is removed on every page refresh.

All the good stuff is in ./app. Start at ./app/main.cjsx

We lint our JavaScript code against a modified version of the AirBnB style guide. Please lint your changes with eslint, using the .eslintrc file at the root of this repo. If you have any questions, do feel free to ask us on GitHub.

While editing, do your best to follow style and architecture conventions already used by the project. The codebase is large, and styles have evolved during its development. Take a look at zooniverse/front-end-monorepo to get an idea of our conventions for organising components.

What to do if it doesn't run

Try npm ci to freshen up your dependencies. And read the warnings, they should tell you if you're using the wrong version of Node or npm or if you're missing any dependencies. If you use docker-compose to build and test the site, you shouldn't run into any problems with the Node version, but docker-compose build will build a new image with a fresh npm ci.

Testing

If you write a new component, write a test. Each component should have its own .spec.js file. The test runner is Mocha and Enzyme is available for testing React components. Mocha throws an error (Illegal import declaration) when compiling coffeescript files that contain ES6 import statements with template strings. Convert these imports to require statements. You can run the tests with npm test.

Deployment

Deployment is handled by Github Action.

Staging

On opening of pull requests, a Github Action is triggered to deploy to a branch staging location. The blob storage location depends on the pull request number, e.g. https://pr-5926.pfe-preview.zooniverse.org.

On push to master, a Github Action is triggered to deploy to master staging found at https://master.pfe-preview.zooniverse.org.

Production

Production deployments are triggered by an update to which commit the production-release tag is pointed to. This tag should be updated via chat ops and then a Github Action will run that builds and uploads the files to our cloud provider found at https://www.zooniverse.org. The production deployment can be run ad hoc in the actions tab as needed if you have the appropriate permissions on the repository, but only do this in an emergency.

Directory structure

Classifier tasks

Each task component class should have a couple static components:

There are also a few hooks into the rest of the classification interface available, if the task needs to render outside the task area.

These hooks can be prefixed with Persist, which will cause them to appear with the task and persist even after the user has moved on to the next task.

Persist{Before,After}Task work the same way, but for the task area. Non-persistent hooks are unnecessary for the task area.

Each component also needs a few static methods:

Task editors

Make sure you call this.props.onChange with the updated task when it changes.

Drawing task tools

Some static methods, called from the MarkInitializer component, which controls the mark's values during the user's first mark-creating action:

A couple helper components are the DrawingToolRoot which handles selected/disabled states and renders sub-task popups, and the DeleteButton and DragHandle, which render consistent controls for drawing tools. There's also a deleteIfOutOfBounds function that should be called after any whole-mark drags.

Conventions

React requires each component in an array to have a sibling-unique key. When rendering arrays of things that do not have IDs (annotations, answers), provide a random _key property if it doesn't exist. Ensure underscore-prefixed properties aren't persisted. That's automatic with the JSONAPIClient.Model class.

<ul>
  {for item in things
    item._key ?= Math.random()
    <li key={item._key}>{item.label}</li>}
</ul>

Async helper components

There are some nice unfortunate (in hindsight) components to help with async values. They take a function as @props.children, which looks a little horsey but works rather nicely. Most requested data is cached locally, so these are usually safe, but if you notice the same request being made multiple times in a row, these are a good place to start looking for the redundant calls. Here's an example of re-rendering when a project changes, which results in checking the projects owners.

<ChangeListener target={@props.project}>{=>
  <PromiseRenderer promise={@props.project.get('owners')}>{([owner]) =>
    if owner is @props.user
      <p>This project is yours.</p>
    else
      <p>This project belongs to {owner.display_name}.</p>
  }</PromiseRenderer>
}</ChangeListener>

Do not write new code using ChangeListener or PromiseRenderer.

If it's reasonable, replace ChangeListener and PromiseRenderer instances with component state in code you work on. It's more verbose, but it's more readable, and it'll get us closer to rendering on the server in the future.

CSS conventions

Include any CSS required for a component's functionality inline in with component, otherwise keep it in a separate file, one per component. For a given component, pick a unique top-level class name for that component and nest child classes under it. Keep common base styles and variables in common.styl. Stylus formatting: Yes colons, no semicolons, no braces. @extends up top, then properties (alphabetically), then descendant selectors. Prefer use of display: flex and flex-wrap: wrap to explicit media queries wherever possible.

Our CSS has gotten really huge, so we're trying out BEM for organization.

// <special-button.styl>
.special-button
  background: red
  color: white

.special-button__icon
  width: 1em;

// <special-container.styl>
.special-container
  margin: 1em 1vw

  .special-container__button
    border: 1px solid

Writing components in ES6/ES2015

We're migrating from coffeescript to ES6. This can be done incrementally by writing a new component or rewriting an existing component in ES6. A few gotchas should be mentioned:

`import NewComponent from './new-component'`

An ESLint configuration file is setup in the root of the repository for you to use with your text editor to lint both ES6 and use Airbnb's React style guide.

A guide on writing native classes versus using createReactClass()

Custom projects

See the panoptes-client library: https://www.npmjs.com/package/panoptes-client.

Format of annotation values

The format of an annotation's value depends on the task used to generate it.

Drawing tool marks

All coordinates are relative to the top-left of the image.

All marks have a tool, which is the index of the tool (e.g. workflow.tasks.T0.tools[0]) used to make the mark.

All marks contain a frame, which is the index of the subject frame (e.g. subject.locations[0]) the mark was made on.

If details tasks are defined for a tool, its marks will have a details array of sub-classifications (each with a value, following the descriptions above).

Drawing annotation value are as follows:

Acknowledgements

Thanks to BrowserStack for supporting open source and allowing us to test this project on multiple platforms.

BrowserStack logo

pullreminders