Skip to content

Commit

Permalink
feat(app-project): remember current workflow in a cookie
Browse files Browse the repository at this point in the history
- Store your selected workflow in a `workflow_id` cookie on the `zooniverse.org` domain, scoped to the current project.
- Set that cookie when you choose a workflow with the `WorkflowSelectButton` component.
- Read the cookie in the Project model, and return it as `project.selectedWorkflow`. Fall back to `project.defaultWorkflow` for backwards compatibility.
- Use `project.selectedWorkflow` on the Classify page, if there's no workflow in the URL.
- Add the project context to tests.
  • Loading branch information
eatyourgreens committed Aug 17, 2024
1 parent 22707c9 commit ca89b0a
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useTranslation } from 'next-i18next'
import { useStores } from '.'

export default function useProjectNavigation(adminMode) {
const { isAdmin, isLoggedIn, defaultWorkflow, slug } = useStores()
const { isAdmin, isLoggedIn, selectedWorkflow, slug } = useStores()
const { t } = useTranslation('components')
const classifyHref = defaultWorkflow ? `/${slug}/classify/workflow/${defaultWorkflow}` : `/${slug}/classify`
const classifyHref = selectedWorkflow ? `/${slug}/classify/workflow/${selectedWorkflow}` : `/${slug}/classify`
const links = [
{
href: `/${slug}/about/research`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function storeMapper(store) {
configuration: {
languages
},
defaultWorkflow,
selectedWorkflow,
display_name: title,
inBeta,
slug
Expand All @@ -32,12 +32,12 @@ function storeMapper(store) {

return {
availableLocales,
defaultWorkflow,
inBeta,
isAdmin,
isLoggedIn,
organizationSlug,
organizationTitle,
selectedWorkflow,
slug,
title
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ function useStore(store) {
const {
appLoadingState,
project: {
experimental_tools
experimental_tools,
selectedWorkflow
},
user: { personalization: { projectPreferences } }
} = store

return {
appLoadingState,
projectPreferences,
selectedWorkflow,
workflowAssignmentEnabled: experimental_tools.includes('workflow assignment')
}
}
Expand All @@ -23,6 +25,7 @@ function ClassifyPageConnector(props) {
const {
appLoadingState,
projectPreferences,
selectedWorkflow,
workflowAssignmentEnabled = false
} = useStore(store)

Expand All @@ -32,6 +35,7 @@ function ClassifyPageConnector(props) {
assignedWorkflowID={projectPreferences?.settings?.workflow_id}
projectPreferences={projectPreferences}
workflowAssignmentEnabled={workflowAssignmentEnabled}
workflowID={props.workflowID || selectedWorkflow}
{...props}
/>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import asyncStates from '@zooniverse/async-states'
import { mount } from 'enzyme'
import { Provider } from 'mobx-react'
import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'

import initStore from '@stores'
import WorkflowSelector from './WorkflowSelector'
import WorkflowSelectButtons from './components/WorkflowSelectButtons'
import { expect } from 'chai'

describe('Component > WorkflowSelector', function () {
let store

const mockRouter = {
asPath: '/zooniverse/snapshot-serengeti/about/team',
basePath: '/projects',
Expand Down Expand Up @@ -42,54 +46,66 @@ describe('Component > WorkflowSelector', function () {
const DEFAULT_WORKFLOW_DESCRIPTION = 'WorkflowSelector.message'
/** The translation function will simply return keys in a testing env */

it('should render without crashing', function () {
const wrapper = mount(
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
workflowDescription={WORKFLOW_DESCRIPTION}
/>
</RouterContext.Provider>
)
expect(wrapper).to.be.ok()
this.beforeEach(function () {
store = initStore(true)
})

describe('workflow description', function () {
it('should use the `workflowDescription` prop if available', function () {
const wrapper = mount(
it('should render without crashing', function () {
const wrapper = mount(
<Provider store={store}>
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
workflowDescription={WORKFLOW_DESCRIPTION}
/>
</RouterContext.Provider>
</Provider>
)
expect(wrapper).to.be.ok()
})

describe('workflow description', function () {
it('should use the `workflowDescription` prop if available', function () {
const wrapper = mount(
<Provider store={store}>
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
workflowDescription={WORKFLOW_DESCRIPTION}
/>
</RouterContext.Provider>
</Provider>
)
expect(wrapper.contains(WORKFLOW_DESCRIPTION)).to.be.true()
})

it('should use the default message if the `workflowDescription` prop is unset', function () {
const wrapper = mount(
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
/>
</RouterContext.Provider>
<Provider store={store}>
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
/>
</RouterContext.Provider>
</Provider>
)
expect(wrapper.contains(DEFAULT_WORKFLOW_DESCRIPTION)).to.be.true()
})

it('should use the default message if the `workflowDescription` prop is an empty string', function () {
const wrapper = mount(
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
workflowDescription=''
/>
</RouterContext.Provider>
<Provider store={store}>
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
theme={THEME}
workflows={WORKFLOWS}
workflowDescription=''
/>
</RouterContext.Provider>
</Provider>
)
expect(wrapper.contains(DEFAULT_WORKFLOW_DESCRIPTION)).to.be.true()
})
Expand All @@ -98,14 +114,16 @@ describe('Component > WorkflowSelector', function () {
describe('when successfully loaded the user state and loaded the user project preferences', function () {
it('should render workflow select buttons', function () {
const wrapper = mount(
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
uppLoaded={true}
userReadyState={asyncStates.success}
theme={THEME}
workflows={WORKFLOWS}
/>
</RouterContext.Provider>
<Provider store={store}>
<RouterContext.Provider value={mockRouter}>
<WorkflowSelector
uppLoaded={true}
userReadyState={asyncStates.success}
theme={THEME}
workflows={WORKFLOWS}
/>
</RouterContext.Provider>
</Provider>
)
expect(wrapper.find(WorkflowSelectButtons)).to.have.lengthOf(1)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { bool, number, object, shape, string } from 'prop-types'
import { useTranslation } from 'next-i18next'
import { useCallback, useContext } from 'react'
import { MobXProviderContext } from 'mobx-react'

import addQueryParams from '@helpers/addQueryParams'
import theme from './theme'

export const ThemedButton = withThemeContext(Button, theme)

function useProject() {
const stores = useContext(MobXProviderContext)
const { project } = stores.store
return project
}
function WorkflowSelectButton ({
disabled = false,
router,
workflow,
...rest
}) {
const { t } = useTranslation('components')
const nextRouter = useRouter()
router = router || nextRouter
const owner = router?.query?.owner
const project = router?.query?.project
const project = useProject()

const url = `/${owner}/${project}/classify/workflow/${workflow.id}`
const url = `/${project.slug}/classify/workflow/${workflow.id}`
const onClick = useCallback(() => {
project.setSelectedWorkflow(workflow.id)
}, [project, workflow.id])

const href = addQueryParams(url)
const completeness = parseInt(workflow.completeness * 100, 10)
Expand Down Expand Up @@ -68,6 +75,7 @@ function WorkflowSelectButton ({
icon={<Next size='15px' />}
reverse
label={label}
onClick={onClick}
primary
{...rest}
/>
Expand Down
Loading

0 comments on commit ca89b0a

Please sign in to comment.