From 6742908af369268fa7b8f81104bcea7435a26953 Mon Sep 17 00:00:00 2001 From: "Shaun A. Noordin" Date: Fri, 1 Mar 2024 12:23:20 +0000 Subject: [PATCH] Pages Editor: implement Associated Subject Sets & fetch more data (#7048) * pages-editor-pt15: prepare WorkflowSettingsPage for setting Subject Sets * Add AssociatedSubjectSets component * DataManager: fetch project AND workflow resources at the same time * DataManager: fix project missing after update. Add documentation about project resource. * TasksPage: dynamically generate Preview URL * AssociatedSubjectSets: fetch Subject Sets. Add status-banner design * DataManager: add status banners for fetching/error states * PagesEditor: tweak style to add vertical padding to main container * AssociatedSubjectSets: enable checkboxes for linking Subject Sets * AssociatedSubjectSets: implement linking/unlinking Subject Sets * WorkflowSettingsPage: style input-group element * WorkflowSettingsPage: remove test sections * PagesEditor: revert default tab to be the Tasks tab * TasksPage: extract getPreviewEnv() into an external helper file * AssociatedSubjectSets: fix typo --- app/pages/lab-pages-editor/DataManager.jsx | 37 +++++-- app/pages/lab-pages-editor/PagesEditor.jsx | 3 +- .../components/TasksPage/TasksPage.jsx | 6 +- .../WorkflowSettingsPage.jsx | 101 ++---------------- .../components/AssociatedSubjectSets.jsx | 82 ++++++++++++++ .../components/WorkflowSettingsPage/index.jsx | 3 + .../lab-pages-editor/helpers/getPreviewEnv.js | 21 ++++ css/lab-pages-editor.styl | 28 ++++- 8 files changed, 177 insertions(+), 104 deletions(-) rename app/pages/lab-pages-editor/components/{ => WorkflowSettingsPage}/WorkflowSettingsPage.jsx (63%) create mode 100644 app/pages/lab-pages-editor/components/WorkflowSettingsPage/components/AssociatedSubjectSets.jsx create mode 100644 app/pages/lab-pages-editor/components/WorkflowSettingsPage/index.jsx create mode 100644 app/pages/lab-pages-editor/helpers/getPreviewEnv.js diff --git a/app/pages/lab-pages-editor/DataManager.jsx b/app/pages/lab-pages-editor/DataManager.jsx index 96a717a1c6..5c64ddd2be 100644 --- a/app/pages/lab-pages-editor/DataManager.jsx +++ b/app/pages/lab-pages-editor/DataManager.jsx @@ -2,6 +2,10 @@ Data Manager Responsible for fetching, updating, and saving Workflow resources from/to the Panoptes service. + +Also fetches the project resource for secondary purposes. (e.g. figuring out +which Subject Sets are available for the workflow, available experimental tools, +and the workflow's preview URL.) */ // ESLint: don't import global React, and don't use .defaultProps. @@ -17,9 +21,11 @@ import { WorkflowContext } from './context.js'; function DataManager({ // key: to ensure DataManager renders FRESH (with states reset) whenever workflowId changes, use children = null, + projectId = '', workflowId = '' }) { const [apiData, setApiData] = useState({ + project: null, workflow: null, status: 'ready' }); @@ -31,33 +37,42 @@ function DataManager({ // Also see general pattern notes: // https://react.dev/reference/react/useEffect#fetching-data-with-effects useEffect(() => { - async function fetchWorkflow() { + async function fetchProjectAndWorkflow() { try { setApiData({ + project: null, workflow: null, status: 'fetching' }); - const wf = await apiClient.type('workflows').get(workflowId); + const [ proj, wf ] = await Promise.all([ + apiClient.type('projects').get(projectId), + apiClient.type('workflows').get(workflowId) + ]); + if (!proj) throw new Error('No project'); if (!wf) throw new Error('No workflow'); setApiData({ + project: proj, workflow: wf, status: 'ready' }); + } catch (err) { console.error('DataManager: ', err); setApiData({ + project: null, workflow: null, status: 'error' }); } } - fetchWorkflow(); + fetchProjectAndWorkflow(); }, [workflowId]); // Listen for workflow changes, and update a counter to prompt useMemo to update the context. + // NOTE: we're not listening for *project* changes, as the DataManager isn't meant to update that resource. useEffect(() => { const wf = apiData.workflow; function onWorkflowChange() { @@ -84,19 +99,21 @@ function DataManager({ const newWorkflow = await wf.update(data).save(); - setApiData({ + setApiData((prevState) => ({ + ...prevState, workflow: newWorkflow, // Note: newWorkflow is actually the same as the old wf, so useMemo will have to listen to status changing instead. status: 'ready' - }); + })); return newWorkflow; } return { + project: apiData.project, workflow: apiData.workflow, update }; - }, [apiData.workflow, updateCounter]); + }, [apiData.project, apiData.workflow, updateCounter]); if (!workflowId) return (
ERROR: no Workflow ID specified
); // if (!workflow) return null @@ -105,7 +122,12 @@ function DataManager({ -
{apiData.status}
+ {apiData.status === 'fetching' && ( +
Fetching data...
+ )} + {apiData.status === 'error' && ( +
ERROR: could not fetch data
+ )} {children}
); @@ -116,6 +138,7 @@ DataManager.propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), + projectId: PropTypes.string, workflowId: PropTypes.string }; diff --git a/app/pages/lab-pages-editor/PagesEditor.jsx b/app/pages/lab-pages-editor/PagesEditor.jsx index 8da9a78a08..e15641e1e5 100644 --- a/app/pages/lab-pages-editor/PagesEditor.jsx +++ b/app/pages/lab-pages-editor/PagesEditor.jsx @@ -14,7 +14,7 @@ import PropTypes from 'prop-types'; import DataManager from './DataManager.jsx'; import WorkflowHeader from './components/WorkflowHeader.jsx'; import TasksPage from './components/TasksPage'; -import WorkflowSettingsPage from './components/WorkflowSettingsPage.jsx'; +import WorkflowSettingsPage from './components/WorkflowSettingsPage'; import strings from './strings.json'; function PagesEditor({ params }) { @@ -37,6 +37,7 @@ function PagesEditor({ params }) {
Associated Subject Sets

Choose the set of subjects you want to use for this workflow. Add subject sets in the Subject Sets tab.

-

TODO

+
@@ -147,99 +147,14 @@ export default function WorkflowSettingsPage() {
-
+
Classification Tools -

TEST: HTML and styling for checkbox input

- - - +

TODO

-
+
Quicktalk -

TEST: HTML and styling for radio options

- - - +

TODO

diff --git a/app/pages/lab-pages-editor/components/WorkflowSettingsPage/components/AssociatedSubjectSets.jsx b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/components/AssociatedSubjectSets.jsx new file mode 100644 index 0000000000..8491bc7f1b --- /dev/null +++ b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/components/AssociatedSubjectSets.jsx @@ -0,0 +1,82 @@ +import { useState, useEffect } from 'react'; + +const ARBITRARY_PAGE_SIZE = 250; // If a project has more than this number of subject sets, then we need a better solution. + +export default function AssociatedSubjectSets({ project, workflow }) { + const [apiData, setApiData] = useState({ + subjectSets: null, + status: 'ready' + }); + const [linkedSubjectSets, setLinkedSubjectSets] = useState(workflow?.links?.subject_sets || []); + + useEffect(() => { + async function fetchSubjectSets() { + try { + setApiData({ + subjectSets: null, + status: 'fetching' + }); + + const ssets = await project.get('subject_sets', { sort: '+id', page_size: ARBITRARY_PAGE_SIZE }); + if (!ssets) throw new Error('No subject sets'); + + setApiData({ + subjectSets: ssets, + status: 'ready' + }); + + } catch (err) { + console.error('AssociatedSubjectSets: ', err); + setApiData({ + subjectSets: null, + status: 'error' + }); + } + } + + fetchSubjectSets(); + }, [project, workflow]); + + function toggleSubjectSet(e) { + const subjectSetId = e?.currentTarget?.dataset?.subjectset; + if (!subjectSetId) return; + const alreadyLinked = linkedSubjectSets.includes(subjectSetId); + + if (alreadyLinked) { // If already linked, remove it. + setLinkedSubjectSets(linkedSubjectSets.filter(sset => sset !== subjectSetId)); + workflow.removeLink('subject_sets', [subjectSetId]); + } else { // If not yet linked, add it. + setLinkedSubjectSets([ ...linkedSubjectSets, subjectSetId]); + workflow.addLink('subject_sets', [subjectSetId]); + } + } + + if (!project || !workflow) return null; + + if (apiData.status === 'fetching') { + return (
Fetching Subject Sets...
) + } + + if (apiData.status === 'error') { + return (
ERROR: couldn't fetch Subject Sets
) + } + + return ( + + ); +} diff --git a/app/pages/lab-pages-editor/components/WorkflowSettingsPage/index.jsx b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/index.jsx new file mode 100644 index 0000000000..209adabf73 --- /dev/null +++ b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/index.jsx @@ -0,0 +1,3 @@ +import WorkflowSettingsPage from './WorkflowSettingsPage.jsx'; + +export default WorkflowSettingsPage; diff --git a/app/pages/lab-pages-editor/helpers/getPreviewEnv.js b/app/pages/lab-pages-editor/helpers/getPreviewEnv.js new file mode 100644 index 0000000000..0203519d00 --- /dev/null +++ b/app/pages/lab-pages-editor/helpers/getPreviewEnv.js @@ -0,0 +1,21 @@ +/* +Figures out of the "Preview Workflow" link requires "?env=staging" or +"?env=production" + */ +export default function getPreviewEnv() { + const hostname = window?.location?.hostname || ''; + const params = new URLSearchParams(window?.location?.search); + const explicitEnv = params.get('env'); + + // If an explicit ?env=... is specified, use that. + if (explicitEnv) return `?env=${explicitEnv}`; + + // The following URLs default to using staging: + // https://local.zooniverse.org:3735/lab/1982/workflows/editor/3711 + // https://pr-7046.pfe-preview.zooniverse.org/lab/1982/workflows/editor/3711?env=staging + if (hostname.match(/^(local|.*\.pfe-preview)\.zooniverse\.org$/ig)) { + return '?env=staging' + } + + return ''; +} \ No newline at end of file diff --git a/css/lab-pages-editor.styl b/css/lab-pages-editor.styl index 47c74216ee..54c64c8bbc 100644 --- a/css/lab-pages-editor.styl +++ b/css/lab-pages-editor.styl @@ -48,7 +48,7 @@ $fontWeightBoldPlus = 700 font-size: $fontSizeM max-width: 840px margin: 0 auto - padding: 0 $sizeM + padding: $sizeM $sizeM // General UI // --------------------------------------------------------------------------- @@ -124,6 +124,17 @@ $fontWeightBoldPlus = 700 .decoration-plus &::after content: ' +' + + .status-banner + display: flex + flex-direction: column + justify-content: center + align-items: center + padding: 0.5em + + &.error + background: $redBright + color: $white // General Layout // --------------------------------------------------------------------------- @@ -236,6 +247,21 @@ $fontWeightBoldPlus = 700 .col-2 grid-column: 2 grid-row: 2 + + ul.input-group + list-style: none + padding: 0 + margin: 0 + + li + display: flex + flex-direction: row + gap: $sizeXS + margin: $sizeXS 0 + padding: 0 + + input[type=checkbox] + margin: $sizeXS // Component: Tasks Page // ---------------------------------------------------------------------------