diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index 1a1233d3d8..d7ebf4330c 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -15,7 +15,7 @@ import StandardLayout from '@shared/components/StandardLayout' import WorkflowAssignmentModal from './components/WorkflowAssignmentModal' import WorkflowMenu from './components/WorkflowMenu' -const ClassifierWrapper = dynamic(() => +export const ClassifierWrapper = dynamic(() => import('./components/ClassifierWrapper'), { ssr: false } ) @@ -32,7 +32,25 @@ function ClassifyPage ({ : ['1em', 'auto', '1em'] const [ workflowFromUrl ] = workflows.filter(workflow => workflow.id === workflowID) - const canClassify = workflowFromUrl?.grouped ? !!subjectSetID : !!workflowID + let subjectSetFromUrl + if (workflowFromUrl && workflowFromUrl.subjectSets) { + [ subjectSetFromUrl ] = workflowFromUrl.subjectSets.filter(subjectSet => subjectSet.id === subjectSetID) + } + // The classifier requires a workflow by default + let canClassify = !!workflowID + // grouped workflows require a subject set + canClassify = workflowFromUrl?.grouped ? !!subjectSetID : canClassify + // indexed subject sets require a subject + canClassify = subjectSetFromUrl?.isIndexed ? !!subjectID : canClassify + + let classifierProps = {} + if (canClassify) { + classifierProps = { + workflowID, + subjectSetID, + subjectID + } + } return ( @@ -46,6 +64,8 @@ function ClassifyPage ({ {!canClassify && ( )} @@ -53,9 +73,7 @@ function ClassifyPage ({ diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js index c18d77693d..2f7721abb2 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js @@ -1,7 +1,7 @@ import { shallow } from 'enzyme' import React from 'react' -import { ClassifyPage } from './ClassifyPage' +import { ClassifyPage, ClassifierWrapper } from './ClassifyPage' import FinishedForTheDay from './components/FinishedForTheDay' import WorkflowMenu from './components/WorkflowMenu' import ThemeModeToggle from '@components/ThemeModeToggle' @@ -84,6 +84,11 @@ describe('Component > ClassifyPage', function () { it('should show a workflow menu', function () { expect(wrapper.find(WorkflowMenu)).to.have.lengthOf(1) }) + + it('should not pass the workflow ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('workflowID')).to.be.undefined() + }) }) describe('with a subject set', function () { @@ -100,6 +105,90 @@ describe('Component > ClassifyPage', function () { it('should not show a workflow menu', function () { expect(wrapper.find(WorkflowMenu)).to.have.lengthOf(0) }) + + it('should pass the workflow ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('workflowID')).to.equal('1234') + }) + + it('should pass the subject set ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('subjectSetID')).to.equal('3456') + }) + }) + + describe('with an indexed subject set', function () { + let workflows = [{ + id: '1234', + grouped: true, + subjectSets: [{ + id: '3456', + displayName: 'indexed set', + isIndexed: true + }] + }] + + describe('without a subject', function () { + let wrapper + + before(function () { + wrapper = shallow( + + ) + }) + + it('should show a workflow menu', function () { + expect(wrapper.find(WorkflowMenu)).to.have.lengthOf(1) + }) + + it('should not pass the workflow ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('workflowID')).to.be.undefined() + }) + + it('should not pass the subject set ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('subjectSetID')).to.be.undefined() + }) + }) + + describe('with a subject', function () { + let wrapper + + before(function () { + wrapper = shallow( + + ) + }) + + it('should not show a workflow menu', function () { + expect(wrapper.find(WorkflowMenu)).to.have.lengthOf(0) + }) + + it('should pass the workflow ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('workflowID')).to.equal('1234') + }) + + it('should pass the subject set ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('subjectSetID')).to.equal('3456') + }) + + it('should pass the subject ID to the classifier', function () { + const classifier = wrapper.find(ClassifierWrapper) + expect(classifier.prop('subjectID')).to.equal('5678') + }) + }) }) }) }) diff --git a/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.js b/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.js index e0ce045351..cc483c4046 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.js +++ b/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.js @@ -12,13 +12,14 @@ import SubjectPicker from '@shared/components/SubjectPicker' */ export default function WorkflowMenu({ headingBackground = 'brand', + subjectSetFromUrl, titleColor = 'neutral-6', workflowFromUrl, workflows }) { const router = useRouter() const { owner, project } = router?.query || {} - const [ activeSubjectSet, setActiveSubjectSet ] = useState() + const [ activeSubjectSet, setActiveSubjectSet ] = useState(subjectSetFromUrl) const [ activeWorkflow, setActiveWorkflow ] = useState(workflowFromUrl) function onSelectSubjectSet(event, subjectSet) { @@ -84,6 +85,11 @@ export default function WorkflowMenu({ ) } +const subjectSetType = shape({ + displayName: string, + id: string +}) + const workflowType = shape({ displayName: string, id: string @@ -94,6 +100,10 @@ WorkflowMenu.propTypes = { Background colour of the title bar. */ headingBackground: string, + /** + An optional selected subject set. If present, we can jump straight to subject selection. + **/ + subjectSetFromUrl: subjectSetType, /** text colour of the title bar. */ diff --git a/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.spec.js b/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.spec.js index bf9fd97f1e..09e6383a53 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/components/WorkflowMenu/WorkflowMenu.spec.js @@ -5,6 +5,7 @@ import WorkflowMenu from './' import WorkflowSelector from '@shared/components/WorkflowSelector' import SubjectSetPicker from '@shared/components/SubjectSetPicker' +import SubjectPicker from '@shared/components/SubjectPicker' describe('Component > ClassifyPage > WorkflowMenu', function () { describe('without a selected workflow', function () { @@ -41,4 +42,34 @@ describe('Component > ClassifyPage > WorkflowMenu', function () { expect(wrapper.find(SubjectSetPicker)).to.have.lengthOf(1) }) }) + + describe('with a selected workflow and subject set', function () { + let wrapper + let workflows = [{ + id: '1234', + grouped: true + }] + let subjectSet = { + id: '345', + displayName: 'test set' + } + + before(function () { + wrapper = shallow( + + ) + }) + + it('should not show a workflow selector', function () { + expect(wrapper.find(WorkflowSelector)).to.have.lengthOf(0) + }) + + it('should show a subject picker', function () { + expect(wrapper.find(SubjectPicker)).to.have.lengthOf(1) + }) + }) }) \ No newline at end of file diff --git a/packages/app-project/src/shared/components/SubjectSetPicker/SubjectSetPicker.js b/packages/app-project/src/shared/components/SubjectSetPicker/SubjectSetPicker.js index 96aba40c26..ea746c97d0 100644 --- a/packages/app-project/src/shared/components/SubjectSetPicker/SubjectSetPicker.js +++ b/packages/app-project/src/shared/components/SubjectSetPicker/SubjectSetPicker.js @@ -32,7 +32,15 @@ function BackButton({ onClick }) { /> ) } -function SubjectSetPicker ({ baseUrl, onClose, onSelect, workflow }) { +/** + Display a list of subject set cards for a workflow. Each card links to the corresponding subject set ID. +*/ +function SubjectSetPicker ({ + baseUrl, + onClose = () => true, + onSelect = () => true, + workflow +}) { const router = useRouter() /* Vertical spacing for the picker instructions. @@ -100,9 +108,21 @@ function SubjectSetPicker ({ baseUrl, onClose, onSelect, workflow }) { } SubjectSetPicker.propTypes = { + /** + Base URL for links eg. `/projects/${owner}/${project}/classify` + */ + baseUrl: string.isRequired, + /** + Callback to close/cancel the picker without taking action. + */ onClose: func, - owner: string.isRequired, - project: string.isRequired, + /** + Callback to call on clicking a subject set card: `onSelect(event, subjectSet)` + */ + onSelect: func, + /** + The selected workflow. + */ workflow: shape({ completeness: number, default: bool, diff --git a/packages/app-project/src/shared/components/SubjectSetPicker/components/SubjectSetCard/SubjectSetCard.js b/packages/app-project/src/shared/components/SubjectSetPicker/components/SubjectSetCard/SubjectSetCard.js index 22a9c59d69..935810ed72 100644 --- a/packages/app-project/src/shared/components/SubjectSetPicker/components/SubjectSetCard/SubjectSetCard.js +++ b/packages/app-project/src/shared/components/SubjectSetPicker/components/SubjectSetCard/SubjectSetCard.js @@ -5,8 +5,16 @@ import getConfig from 'next/config' import { array, number, string } from 'prop-types' import React from 'react' -function SubjectSetCard (props) { - const { availableSubjects, display_name, id, set_member_subjects_count, subjects } = props +/** + Summary card for a subject set, showing a preview subject, the set name, total subject count and completeness percentage. +*/ +function SubjectSetCard ({ + availableSubjects, + display_name, + id, + set_member_subjects_count, + subjects = [] +}) { const [subject] = subjects const { publicRuntimeConfig = {} } = getConfig() || {} const assetPrefix = publicRuntimeConfig.assetPrefix || '' @@ -69,14 +77,26 @@ function SubjectSetCard (props) { } SubjectSetCard.propTypes = { - display_name: string.required, - id: string.required, - set_member_subjects_count: number.required, + /** + The number of subjects available to be classified. + */ + availableSubjects: number.isRequired, + /** + Subject set title. + */ + display_name: string.isRequired, + /** + Subject set ID. + */ + id: string.isRequired, + /** + Total subject count + */ + set_member_subjects_count: number.isRequired, + /** + Preview subjects. Used to show a preview image. + */ subjects: array } -SubjectSetCard.defaultProps = { - subjects: [] -} - export default SubjectSetCard \ No newline at end of file