Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove @inject from subject viewers #2719

Merged
merged 7 commits into from
Feb 15, 2022

Conversation

eatyourgreens
Copy link
Contributor

@eatyourgreens eatyourgreens commented Jan 31, 2022

Remove @inject from BarChartViewer, LightCurveViewer and SubjectGroupViewer. Convert each from a class to a functional component with hooks. I've added a useJSONData hook to load JSON data for BCV and LCV subjects.

The enzyme tests were written to test implementation details, like internal class state or lifecycle methods. I've rewritten them to test the rendered subject viewer wrapper, and its props, either onError or onReady.

Test projects:

Package:
lib-classifier

Towards #2712.

Review Checklist

General

  • Are the tests passing locally and on Travis?
  • Is the documentation up to date?

Components

Apps

  • Does it work in all major browsers: Firefox, Chrome, Edge, Safari?
  • Does it work on mobile?
  • Can you yarn panic && yarn bootstrap or docker-compose up --build and app works as expected?

Publishing

  • Is the changelog updated?
  • Are the dependencies updated for apps and libraries that are using the newly published library?

Post-merging

@eatyourgreens eatyourgreens added the refactor Refactoring existing code label Jan 31, 2022
@eatyourgreens eatyourgreens force-pushed the remove-inject-subject-viewers branch 9 times, most recently from bf7ea84 to d86303d Compare January 31, 2022 17:17
Comment on lines +93 to +113
export function SubjectGroupViewerContainer({
addAnnotation,
annotation,
cellWidth,
cellHeight,
cellStyle,
gridColumns,
gridRows,
gridMaxWidth,
gridMaxHeight,
ImageObject = window.Image,
interactionMode = 'annotate',
isCurrentTaskValidForAnnotation,
loadingState = asyncStates.initialized,
onError = () => true,
onKeyDown = () => true,
onReady = () => true,
setOnPan = () => true,
setOnZoom = () => true,
subject = undefined
}) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subject group viewer has a lot of props!

let nockScope
let wrapper
before(function () {
sinon.stub(console, 'error')
cdmSpy = sinon.spy(BarChartViewerContainer.prototype, 'componentDidMount')
onErrorSpy = sinon.spy()
nockScope = nock('http://localhost:8080')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the project app tests, we mock the Panoptes API with nock. Maybe that would be better for the subject viewer tests too?

let wrapper

before(function () {
cdmSpy = sinon.spy(BarChartViewerContainer.prototype, 'componentDidMount')
cduSpy = sinon.spy(BarChartViewerContainer.prototype, 'componentDidUpdate')
nockScope = nock('http://localhost:8080')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about mocking panoptes-staging.zooniverse.org or panoptes.zooniverse.org here.

import { zip } from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import request from 'superagent'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goodbye, superagent!

I was going to ask if we shouldn't also remove this from package.json, then I realised that DataImageViwer(?), SingleTextViewer(?), ScatterPlotViewer, and VariableStarViewer still uses the dependency, dangit. I didn't even realise those first two viewers existed.

Copy link
Contributor Author

@eatyourgreens eatyourgreens Feb 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SingleTextViewer inspired me to write this PR, when I saw that it was still using outdated code from when we first started the monorepo.

@@ -0,0 +1,17 @@
# useJSONData

A custom hook which loads JSON data for a Panoptes subject.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] While this is true, the description is missing one important detail: useJSONData loads JSON data for a Subject, and assumes that Subject that has exactly one JSON(-ish) URL data.

Specifically:

  • A text file "technically" counts as a JSON URL (hence, JSON-ish ) for this function, because the Panoptes CLI has (had?) issues differentiating JSON/TXT MIME types.
  • If a Subject has multiple JSON(-ish) URLs, only the first is pulled by useJSONData()

This doesn't matter in current (2018 to 2022) LightCurveViewer (or BarChartViewer, I guess) scenarios, since project owners who use these Viewers know the precise Subject format required. However, I can imagine an edge case where useJSONData doesn't work as described, e.g. when we have a Subject that looks like...

subjectWithTextAndJSON.locations = {
  'text/plain': 'actual-boring-plain-text.txt',
  'application/json': 'json-object-for-data-viewer.json'
}
// In this case, the function attempts to load the plain text as JSON, fails at response.json(), and then skips the actual JSON object file.

I'm marking this as a minor thing right now (⭐ I think editing the README text to include the caveats above should suffice), because if a future theoretical SuperDataViewer needs to use this function to get a Subject with 10 JSON files and a dozen TXT files, we can adjust the code accordingly when it's less theoretical.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really good point, but I think this can wait until we have real examples of subjects, rather than try to second guess what they might look like now.

@shaunanoordin
Copy link
Member

Running manual tests now for SubjectGroupViewer and LightCurveViewer, brb

@eatyourgreens eatyourgreens force-pushed the remove-inject-subject-viewers branch 2 times, most recently from 6d7f5f2 to 93e8697 Compare February 11, 2022 16:49
@shaunanoordin
Copy link
Member

shaunanoordin commented Feb 11, 2022

Quick Review

I've encountered a problem with the LightCurveViewer, but I'm not sure what's the cause. I seem to be able to replicate it consistently.

Issue 1: when switching between Annotate and Move modes on the LCV, the zoom level resets (❗)

Update: Solved

fem-pr2719-20220211-1646-issue-1-v2.mov

(EDIT: changed to a cleaner video example.) In the video above, I'm zooming in (on a time span between Day 18 and 19), but when I try to make an annotation, the zoom takes me back to the default zoom (the time span of Day 0 to 30)

Testing Setup:

Testing steps:

  • Open the Classifier. You'll start in 'Annotate mode' (the pointing arrow icon)
  • Click on the 'Move mode' button (four-direction arrow icon) on the Toolbar
  • Use the mouse to zoom in (mouse wheel) and scroll to (mouse drag) look at an interesting position X on the Subject.
  • Click on the 'Annotation mode' button on the Toolbar
    • Expected: you should STILL be zoomed in on the Interesting Position X on the Subject, so you can place an annotation mark precisely there
    • Actual: the zoom level gets reset to 0

Notes:

  • There are small scenarios where this bug doesn't get triggered, but I haven't been able to fully document those cases yet. e.g. if you use keyboard (left/right, +/-) in Annotation mode to navigate, instead of switching to Move mode and using the mouse to navigate, then you might not notice the problem at all.
  • I'll also need to double check on master to see if I'm missing something (UPDATE: no issues on master, commit b236963)

Status: major bug

Update: Solved

Testing ongoing to pick up anything other issues before I circle around to see if I can figure out wtf is going on here. One thing to note is that the D3.js has its own "internal memory" for zoom/pan values (IIRC), so something is making the Viewer think it's refreshing and thus triggering the D3.js zoom reset.

@eatyourgreens
Copy link
Contributor Author

Isn't move mode controlled by a prop? Changing a component prop would certainly trigger a render, although I'm not sure why it would reset zoom.

@eatyourgreens
Copy link
Contributor Author

@shaunanoordin this code generates a new dataPoints array from JSONdata on each render. The LCV is set up to reset the chart if dataPoints changes. That might explain the bug.

@shaunanoordin
Copy link
Member

Ah, here we go! I see a potential lead.

  • When switching between Annotation mode and Move mode (or vice versa), this triggers LightCurveViewer.componentDidUpdate()
  • There's a check in that function for if (!sameSubject) {}
  • If that triggers, clearChart() is called, which resets the zoom. (i.e. yo you just received a new Subject, please reset the zoom)

I'm checking that !sameSubjcet condition now

@shaunanoordin
Copy link
Member

Ha! OK, looks like we've come to the same conclusion 👍 Should be an easy fix, we can just pass in the Subject ID to the LCV and use that to trigger sameSubject?

@eatyourgreens
Copy link
Contributor Author

@shaunanoordin already done! See the latest commit.

@shaunanoordin
Copy link
Member

I see your changes, Jim! 👍 I'll run tests now, brb!

@shaunanoordin
Copy link
Member

shaunanoordin commented Feb 11, 2022

Quick Review (Update)

LightCurveViewer is looking good! 👍

  • Issue 1 has been solved
  • I can view a valid LCV Subject
  • I can switch to Move mode to navigate with a mouse (pan with mouse drag, zoom with mouse scroll)
  • I can switch to Annotate mode to place an annotation mark using a mouse (mouse drag).
    • In Annotate mode, I can move (mouse drag), edit (mouse drag edges of marks), and delete (click on 'X' button) existing marks
  • I can navigate the LCV using keyboard keys (left/right keys to pan, +/- keys to zoom)
  • I can zoom in/out on the LCV using the Image Toolbar's zoom in/out buttons.
  • I can submit Classifications
    • When I submit a Classification, the zoom levels reset when the next Subject loads.

(Cripes, I need to turn those 👆 into actual component tests)

@shaunanoordin
Copy link
Member

Quick Review (Update)

OK, SubjectGroupViewer (Grid Tool) now!

Issue 2: zoom and pan (keyboard only) don't work on the Subject Group Viewer

The problem:

  • When I try to zoom in, I can only zoom in "one step" at most. (sometimes like from 1.0x to 1.1x, and that's it)
  • When I try to pan (using keyboard), I can only pan "one step" at most (I can go x-1 y+0 ; OR x+0 y-1 ; OR x+1 y+0 ; OR x+0 y+1. Literally only four possible specific pan values!)
    • No issues with drag-to-move, surprisingly.

Analysis:

  • It seems that for the actions listed above, the zoom/pan levels RESET to default, then apply the changes.
  • e.g. when zooming in, every 'zoom' action first RESETS the zoom to 1.0, then adds the zoom in step of +0.1, thus locking it at 1.1
  • e.g. when panning to the right, every 'right key' press first RESETS the pan to x+0 y+0, then adds x+1, thus locking it to x+1 y+0

Test URL: https://local.zooniverse.org:8080/?env=production&project=zookeeper/galaxy-zoo-weird-and-wonderful&demo=true
Baseline: https://www.zooniverse.org/projects/zookeeper/galaxy-zoo-weird-and-wonderful/classify/workflow/18510 (no issues on live)

Tested on/with localhost, macOS, Chrome 98, lib-classifier.

I'll do a quick check on the code to see what's up, but I may need to follow up the debug on Monday

Additional Notes

Full functionality checklist for Subject Group Viewer:

  • ✔️ I can view a Subject consisting of a grid images
    • ✔️ The grid is configured as per the workflow (4x4 cells, in this case)
  • ✔️ In Annotation mode, if there's a SubjectGroupComparisonTask active, I can click on grid cells to 'select' images.
    • ✔️ I can submit Classifications with the selected images
  • In Move mode, I should be able to use the mouse to navigate the Subject
    • ✔️ I can pan the subject using click and drag
    • ❌ I should be able to zoom in/out using the mouse wheel
  • Using the keyboard, I should be able to navigate the Subject (all images should pan & zoom simultaneously)
    • ❌ I should be able to pan the subject using up/down/left/right keys
    • ❌ I should be able to zoom in/out using the +/- keys
  • ❌ Using the Image Tool bar's buttons, I should be able to zoom in/out

@shaunanoordin
Copy link
Member

Yup, I can see that for SubjectGroupViewerContainer, the following state values:

const [panX, setPanX] = useState(0)
const [panY, setPanY] = useState(0)
const [zoom, setZoom] = useState(1)

...keep resetting to their default values for some reason. I haven't been able to trace why, unfortunately. This is now a Monday problem!

@eatyourgreens
Copy link
Contributor Author

Remove `@inject` from `BarChartViewer`, `LightCurveViewer` and `SubjectGroupViewer`. Convert each from a class to a functional component with hooks.
Try to test rendered output, either `onError` or `onReady`.
Move common data fetching code into a custom hook for JSON data subjects.
Pass `subjectID` so that we can test whether the subject has changed.
Use callbacks to update state from previous state.
@eatyourgreens
Copy link
Contributor Author

@shaunanoordin I've fixed those problems by using callbacks when state is changing continuously.

@shaunanoordin
Copy link
Member

I think this is what’s happening:
https://beta.reactjs.org/apis/usestate#updating-state-based-on-the-previous-state

Oh, that's new to me! I've never seen setVal(val + 1) with a function passed in, like setVal(_val => _val + 1).

Thanks Jim! I'll wrap up testing later tonight

Copy link
Member

@shaunanoordin shaunanoordin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review

Package: lib-classifier

This PR is primarily a refactor/code cleanup for the following Subject Viewer: BarChartViewer, LightCurveViewer, and SubjectGroupViewer (aka Grid Tool)

  • The container components have been rewritten as functional component.
  • Notable code changes:
    • State-changing hooks that increment existing state values use this format: setValue(_val => _val + 1) and not this: setValue(val + 1)
    • LightCurveViewer now checks for Subject IDs to determine if it's looking at the same Subject.

Code changes look good, and testing for both LightCurveViewer and Grid Tool look great. I can't seem to find a live project that uses BarChartViewer, so I have to rely on Storybook for that.

Testing

Tested on localhost with various projects, with macOS + Chrome.

Testing for LightCurveViewer

Testing for SubjectGroupViewer

Testing for BarChartViewer

  • Tested on Storybook
  • ✅ Functionality checklist:
    • I'm able view the Subject displayed as a bar chart
    • I'm able to hover my mouse across different bars to see their numerical values.
    • I'm able to tab-navigate across different bars (and see their numerical values when I have focus)

Status

LGTM 👍

@github-actions github-actions bot added the approved This PR is approved for merging label Feb 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved This PR is approved for merging refactor Refactoring existing code
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants