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

Project app: use getServerSideProps on all project pages #1823

Merged
merged 10 commits into from
Jan 7, 2021
44 changes: 0 additions & 44 deletions packages/app-project/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Box } from 'grommet'
import makeInspectable from 'mobx-devtools-mst'
import { Provider } from 'mobx-react'
import { getSnapshot } from 'mobx-state-tree'
import App from 'next/app'
import React from 'react'
import { createGlobalStyle } from 'styled-components'
Expand All @@ -23,40 +22,6 @@ const GlobalStyle = createGlobalStyle`
initializeLogger()

export default class MyApp extends App {
static async getInitialProps ({ Component, router, ctx: context }) {
let pageProps = {
host: generateHostUrl(context),
isServer: !!context.req,
query: context.query
}

if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(context)
}

if (pageProps.isServer) {
// cookie is in the next.js context req object
const mode = getCookie(context, 'mode') || undefined
const dismissedAnnouncementBanner = getCookie(context, 'dismissedAnnouncementBanner') || undefined
const store = initStore(pageProps.isServer, {
ui: {
dismissedAnnouncementBanner,
mode
}
})

const { owner, project } = context.query
if (owner && project) {
const projectSlug = `${owner}/${project}`
const query = (context.query.env) ? { env: context.query.env } : {}
await store.project.fetch(projectSlug, query)
pageProps.initialState = getSnapshot(store)
}
}

return { pageProps }
}

constructor (props) {
super()
const { isServer, initialState } = props.pageProps
Expand Down Expand Up @@ -95,12 +60,3 @@ export default class MyApp extends App {
}
}

function generateHostUrl (context) {
if (context.req) {
const { connection, headers } = context.req
const protocol = connection.encrypted ? 'https' : 'http'
return `${protocol}://${headers.host}`
} else {
return location.origin
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
import getDefaultPageProps from '@helpers/getDefaultPageProps'
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,7 +1,9 @@
import getDefaultPageProps from '@helpers/getDefaultPageProps'
export { default } from '@screens/ClassifyPage'

export function getServerSideProps({ params, req, res }) {
export async function getServerSideProps({ params, query, req, res }) {
const { props: defaultProps } = await getDefaultPageProps({ params, query, req, res })
const { workflowID } = params
const props = { workflowID }
const props = { ...defaultProps, workflowID }
return ({ props })
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import getDefaultPageProps from '@helpers/getDefaultPageProps'
export { default } from '@screens/ClassifyPage'

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

6 changes: 3 additions & 3 deletions packages/app-project/src/helpers/getCookie/getCookie.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import cookie from 'cookie'

export default function getCookie (context, name) {
if (context && context.req && context.req.headers.cookie) {
const parsedCookie = cookie.parse(context.req.headers.cookie)
export default function getCookie (req, name) {
if (req?.headers?.cookie) {
const parsedCookie = cookie.parse(req.headers.cookie)
if (parsedCookie && parsedCookie[name]) {
return parsedCookie[name]
}
Expand Down
20 changes: 10 additions & 10 deletions packages/app-project/src/helpers/getCookie/getCookie.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,37 @@ describe('Helper > getCookie', function () {
})

it('should return an empty string if the response does not have a cookie', function () {
const value = getCookie({ req: { headers: { foo: 'bar' } } })
const value = getCookie({ headers: { foo: 'bar' } })
expect(value).to.be.a('string')
expect(value).to.have.lengthOf(0)
})

it('should return an empty string if a name parameter is not defined', function () {
const cookie = 'mode=light; path=/; max-age=31536000'
const context = {
req: { headers: { cookie } }
const req = {
headers: { cookie }
}
const value = getCookie(context)
const value = getCookie(req)
expect(value).to.be.a('string')
expect(value).to.have.lengthOf(0)
})

it('should return an empty string if the cookie does not contain a value for the name parameter', function () {
const cookie = 'mode=light; path=/; max-age=31536000'
const context = {
req: { headers: { cookie } }
const req = {
headers: { cookie }
}
const value = getCookie(context, 'foo')
const value = getCookie(req, 'foo')
expect(value).to.be.a('string')
expect(value).to.have.lengthOf(0)
})

it('should return the value for the specified name parameter', function () {
const cookie = 'mode=light; path=/; max-age=31536000'
const context = {
req: { headers: { cookie } }
const req = {
headers: { cookie }
}
const value = getCookie(context, 'mode')
const value = getCookie(req, 'mode')
expect(value).to.be.a('string')
expect(value).to.have.equal('light')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import getCookie from '@helpers/getCookie'
import initStore from '@stores'
import { getSnapshot } from 'mobx-state-tree'

export default async function getDefaultPageProps({ params, query, req, res }) {

// cookie is in the next.js context req object
const mode = getCookie(req, 'mode') || null
const dismissedAnnouncementBanner = getCookie(req, 'dismissedAnnouncementBanner') || null
const store = initStore({
ui: {
dismissedAnnouncementBanner,
mode
}
})

if (params.owner && params.project) {
const { owner, project } = params
const projectSlug = `${owner}/${project}`
const { env } = query
await store.project.fetch(projectSlug, { env })
}

const { project, ui } = getSnapshot(store)
const { headers, connection } = req
const props = {
host: generateHostUrl(headers, connection),
initialState: {
project,
ui
},
isServer: true,
query
}

return { props }
}

function generateHostUrl(headers, connection) {
const protocol = connection.encrypted ? 'https' : 'http'
return `${protocol}://${headers.host}`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './getDefaultPageProps';
18 changes: 17 additions & 1 deletion packages/app-project/stores/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ const Project = types
return `/projects/${self.slug}`
},

get defaultWorkflow() {
const activeWorkflows = self.links['active_workflows']
let singleActiveWorkflow
if (activeWorkflows.length === 1) {
[singleActiveWorkflow] = self.links['active_workflows']
}
const defaultWorkflow = self.configuration['default_workflow']
return singleActiveWorkflow || defaultWorkflow
},

get displayName () {
return self.display_name
},
Expand Down Expand Up @@ -95,7 +105,13 @@ const Project = types
'urls',
'workflow_description'
]
properties.forEach(property => { self[property] = project[property] })
properties.forEach(property => {
try {
self[property] = project[property]
} catch (error) {
console.error(`project.${property} is invalid`, error)
}
})

self.loadingState = asyncStates.success
} catch (error) {
Expand Down
65 changes: 65 additions & 0 deletions packages/app-project/stores/Project.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,69 @@ describe('Stores > Project', function () {
.then(done, done)
})
})

describe('default workflow', function () {
describe('with a single active workflow', function () {
let project

before(function () {
const rootStore = Store.create({
project: {
links: {
active_workflows: [ '1234' ]
}
}
}, placeholderEnv)
project = rootStore.project
})

it('should be the active workflow', function () {
const [ singleActiveWorkflow ] = project.links.active_workflows
expect(project.defaultWorkflow).to.equal(singleActiveWorkflow)
})
})

describe('with a default workflow', function () {
let project

before(function () {
const rootStore = Store.create({
project: {
configuration: {
default_workflow: '5678'
},
links: {
active_workflows: [ '1234', '5678' ]
}
}
}, placeholderEnv)
project = rootStore.project
})

it('should be the configured workflow', function () {
expect(project.defaultWorkflow).to.exist()
expect(project.defaultWorkflow).to.equal(project.configuration.default_workflow)
})
})

describe('with neither', function () {
let project

before(function () {
const rootStore = Store.create({
project: {
configuration: {},
links: {
active_workflows: []
}
}
}, placeholderEnv)
project = rootStore.project
})

it('should be undefined', function () {
expect(project.defaultWorkflow).to.be.undefined()
})
})
})
})
2 changes: 1 addition & 1 deletion packages/app-project/stores/UI.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const canSetCookie = process.browser || process.env.BABEL_ENV === 'test'

const UI = types
.model('UI', {
dismissedProjectAnnouncementBanner: types.maybe(types.number),
dismissedProjectAnnouncementBanner: types.maybeNull(types.number),

// The mode is retrieved out of the cookie in _app.js during store initialization
mode: types.optional(types.enumeration('mode', ['light', 'dark']), 'light')
Expand Down
4 changes: 2 additions & 2 deletions packages/app-project/stores/UI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ describe('Stores > UI', function () {
})

it('should contain a dismissedProjectAnnouncementBanner property', function () {
expect(store.dismissedProjectAnnouncementBanner).to.be.undefined()
expect(store.dismissedProjectAnnouncementBanner).to.be.null()
})

it('should have a `dismissProjectAnnouncementBanner` action', function () {
const expectedValue = stringHash(PROJECT.configuration.announcement)
expect(store.dismissedProjectAnnouncementBanner).to.equal(undefined)
expect(store.dismissedProjectAnnouncementBanner).to.be.null()
store.dismissProjectAnnouncementBanner()
expect(store.dismissedProjectAnnouncementBanner).to.equal(expectedValue)
})
Expand Down