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

Render project workflows on the server #1809

Merged
merged 13 commits into from
Jan 8, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,5 @@ export { default } from '@screens/ClassifyPage'

export async function getServerSideProps({ params, query, req, res }) {
const { props } = await getDefaultPageProps({ params, query, req, res })
const { project } = props.initialState
const workflowID = project.defaultWorkflow
if (workflowID) {
props.workflowID = workflowID
}
return ({ props })
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import getDefaultPageProps from '@helpers/getDefaultPageProps'
export { default } from '@screens/ProjectHomePage'
export { default as getServerSideProps } from '@helpers/getDefaultPageProps'

export async function getServerSideProps({ params, query, req, res }) {
const { props } = await getDefaultPageProps({ params, query, req, res })
return ({ props })
}

Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { panoptes } from '@zooniverse/panoptes-js'

async function fetchWorkflowData (activeWorkflows) {
async function fetchWorkflowData (activeWorkflows, env) {
const response = await panoptes
.get('/workflows', {
complete: false,
env,
fields: 'completeness,grouped',
id: activeWorkflows.join(','),
include: 'subject_sets'
})
const { workflows, linked } = response.body
const subjectSets = linked ? linked.subject_sets : []
await Promise.allSettled(subjectSets.map(fetchPreviewImage))
await Promise.allSettled(subjectSets.map(subjectSet => fetchPreviewImage(subjectSet, env)))
return { subjectSets, workflows }
}

function fetchDisplayNames (language, activeWorkflows) {
function fetchDisplayNames (language, activeWorkflows, env) {
return panoptes
.get('/translations', {
env,
fields: 'strings,translated_id',
language,
'translated_id': activeWorkflows.join(','),
Expand All @@ -26,9 +28,10 @@ function fetchDisplayNames (language, activeWorkflows) {
.then(createDisplayNamesMap)
}

async function fetchPreviewImage (subjectSet) {
async function fetchPreviewImage (subjectSet, env) {
const response = await panoptes
.get('/set_member_subjects', {
env,
subject_set_id: subjectSet.id,
include: 'subject',
page_size: 1
Expand All @@ -37,10 +40,10 @@ async function fetchPreviewImage (subjectSet) {
subjectSet.subjects = linked.subjects
}

async function fetchWorkflowsHelper (language = 'en', activeWorkflows, defaultWorkflow) {
const { subjectSets, workflows } = await fetchWorkflowData(activeWorkflows)
async function fetchWorkflowsHelper (language = 'en', activeWorkflows, defaultWorkflow, env) {
const { subjectSets, workflows } = await fetchWorkflowData(activeWorkflows, env)
const workflowIds = workflows.map(workflow => workflow.id)
const displayNames = await fetchDisplayNames(language, workflowIds)
const displayNames = await fetchDisplayNames(language, workflowIds, env)

return workflows.map(workflow => {
const isDefault = workflows.length === 1 || workflow.id === defaultWorkflow
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getCookie from '@helpers/getCookie'
import fetchWorkflowsHelper from '@helpers/fetchWorkflowsHelper'
import initStore from '@stores'
import { getSnapshot } from 'mobx-state-tree'

Expand All @@ -25,14 +26,24 @@ export default async function getDefaultPageProps({ params, query, req, res }) {

const { project, ui } = getSnapshot(store)
const { headers, connection } = req
const { env } = query
const language = project.primary_language
const { active_workflows, default_workflow } = project.links
const workflows = await fetchWorkflowsHelper(language, active_workflows, default_workflow, env)
const props = {
host: generateHostUrl(headers, connection),
initialState: {
project,
ui
},
isServer: true,
query
query,
workflows
}

const workflowID = store.project.defaultWorkflow
if (workflowID) {
props.workflowID = workflowID
}

return { props }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import nock from 'nock'

import getDefaultPageProps from './'

describe('Components > ProjectHomePage > getDefaultPageProps', function () {
const PROJECT = {
id: '1',
default_workflow: '1',
primary_language: 'en',
slug: 'test-owner/test-project',
links: {
active_workflows: ['1']
}
}

const TRANSLATION = {
translated_id: 1,
strings: {
display_name: 'Foo'
}
}

const WORKFLOW = {
id: '1',
completeness: 0.4,
grouped: false,
links: {
subject_sets: ['1', '2', '3']
}
}

function subjectSet(id) {
return {
id,
display_name: `test set ${id}`,
set_member_subjects_count: 10
}
}

describe('with the staging API', function () {
before(function () {
const slug = 'test-owner/test-project'
const scope = nock('https://panoptes-staging.zooniverse.org/api')
.get('/projects')
.query(query => query.slug === slug)
.reply(200, {
projects: [PROJECT]
})
.get('/translations')
.query(query => {
return query.translated_type === 'workflow'
&& query.translated_id === '1'
&& query.language === 'en'
})
.reply(200, {
translations: [TRANSLATION]
})
.get('/workflows')
.query(query => query.id === '1')
.reply(200, {
workflows: [WORKFLOW],
linked: {
subject_sets: [
subjectSet('1'),
subjectSet('2'),
subjectSet('3')
]
}
})
})

it('should return the project\'s active workflows', async function () {
const params = {
owner: 'test-owner',
project: 'test-project'
}
const query = {
env: 'staging'
}
const req = {
connection: {
encrypted: true
},
headers: {
host: 'www.zooniverse.org'
}
}
const res = {}
const { props } = await getDefaultPageProps({ params, query, req, res })
expect(props.workflows).to.deep.equal([
{
completeness: 0.4,
default: true,
grouped: false,
id: '1',
displayName: 'Foo',
subjectSets: [
subjectSet('1'),
subjectSet('2'),
subjectSet('3')
]
}
])
})
})

describe('with the production API', function () {
before(function () {
const slug = 'test-owner/test-project'
const scope = nock('https://www.zooniverse.org/api')
.get('/projects')
.query(query => query.slug === slug)
.reply(200, {
projects: [PROJECT]
})
.get('/translations')
.query(query => {
return query.translated_type === 'workflow'
&& query.translated_id === '1'
&& query.language === 'en'
})
.reply(200, {
translations: [TRANSLATION]
})
.get('/workflows')
.query(query => query.id === '1')
.reply(200, {
workflows: [WORKFLOW],
linked: {
subject_sets: [
subjectSet('1'),
subjectSet('2'),
subjectSet('3')
]
}
})
})

it('should return the project\'s active workflows', async function () {
const params = {
owner: 'test-owner',
project: 'test-project'
}
const query = {
env: 'production'
}
const req = {
connection: {
encrypted: true
},
headers: {
host: 'www.zooniverse.org'
}
}
const res = {}
const { props } = await getDefaultPageProps({ params, query, req, res })
expect(props.workflows).to.deep.equal([
{
completeness: 0.4,
default: true,
grouped: false,
id: '1',
displayName: 'Foo',
subjectSets: [
subjectSet('1'),
subjectSet('2'),
subjectSet('3')
]
}
])
})
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Grid } from 'grommet'
import React from 'react'
import { bool } from 'prop-types'
import { arrayOf, bool, shape, string } from 'prop-types'
import styled from 'styled-components'
import { ZooFooter } from '@zooniverse/react-components'

Expand All @@ -24,14 +24,17 @@ const RemainingHeightBox = styled(Box)`
flex-grow: 1;
`

function ProjectHomePage ({ inBeta }) {
function ProjectHomePage ({
inBeta,
workflows
}) {
return (
<Box border={(inBeta) ? { color: 'brand', size: 'medium' } : false}>
<Media at='default'>
<ZooHeaderWrapper />
<ProjectHeader />
<Announcements />
<Hero />
<Hero workflows={workflows} />
<Box margin='small' gap='small'>
<ThemeModeToggle />
<ZooniverseTalk />
Expand All @@ -48,7 +51,7 @@ function ProjectHomePage ({ inBeta }) {
<ProjectHeader />
<Announcements />
<RemainingHeightBox>
<Hero isWide={true} />
<Hero workflows={workflows} isWide={true} />
</RemainingHeightBox>
</FullHeightBox>
<Box
Expand Down Expand Up @@ -76,11 +79,15 @@ function ProjectHomePage ({ inBeta }) {
}

ProjectHomePage.defaultProps = {
inBeta: false
inBeta: false,
workflows: []
}

ProjectHomePage.propTypes = {
inBeta: bool
inBeta: bool,
workflows: arrayOf(shape({
id: string.isRequired
}))
}

export default ProjectHomePage
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { inject, observer } from 'mobx-react'
import { bool } from 'prop-types'
import { arrayOf, bool, shape, string } from 'prop-types'
import React, { Component } from 'react'

import ProjectHomePage from './ProjectHomePage'
Expand All @@ -22,11 +22,15 @@ class ProjectHomePageContainer extends Component {
}

ProjectHomePageContainer.defaultProps = {
inBeta: false
inBeta: false,
workflows: []
}

ProjectHomePageContainer.propTypes = {
inBeta: bool
inBeta: bool,
workflows: arrayOf(shape({
id: string.isRequired
}))
}

export default ProjectHomePageContainer
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { arrayOf, bool, shape, string } from 'prop-types'
import React, { Component } from 'react'
import asyncStates from '@zooniverse/async-states'

import WideLayout from './components/WideLayout'
import NarrowLayout from './components/NarrowLayout'

function Hero (props) {
const { isWide, workflows } = props
return isWide
? <WideLayout workflows={workflows} />
: <NarrowLayout workflows={workflows} />
}

Hero.propTypes = {
isWide: bool,
workflows: arrayOf(shape({
id: string.isRequired
}))
}

Hero.defaultProps = {
isWide: false,
workflows: []
}
export default Hero
Loading