From 11e42930c9c9182fc3a8e9cb967e3bbfd18c1c04 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Thu, 4 May 2023 15:49:57 -0500 Subject: [PATCH 01/12] Add fetchOrganization helper --- .../fetchOrganization/fetchOrganization.js | 42 +++++ .../fetchOrganization.spec.js | 86 ++++++++++ .../src/helpers/fetchOrganization/index.js | 1 + .../getDefaultPageProps.js | 6 +- .../getStaticPageProps/getStaticPageProps.js | 15 +- .../getStaticPageProps.spec.js | 148 ++++++++++++++++++ packages/app-project/src/helpers/index.js | 1 + .../ProjectHomePage/ProjectHomePage.js | 7 + 8 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 packages/app-project/src/helpers/fetchOrganization/fetchOrganization.js create mode 100644 packages/app-project/src/helpers/fetchOrganization/fetchOrganization.spec.js create mode 100644 packages/app-project/src/helpers/fetchOrganization/index.js diff --git a/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.js b/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.js new file mode 100644 index 0000000000..e35d305d6f --- /dev/null +++ b/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.js @@ -0,0 +1,42 @@ +import { panoptes } from '@zooniverse/panoptes-js' + +import fetchTranslations from '@helpers/fetchTranslations' +import getServerSideAPIHost from '@helpers/getServerSideAPIHost' +import logToSentry from '@helpers/logger/logToSentry.js' + +async function fetchOrganizationData(organizationID, env) { + const { headers, host } = getServerSideAPIHost(env) + try { + const query = { + env, + id: organizationID, + listed: true + } + const response = await panoptes.get('/organizations', query, { ...headers }, host) + const [ organization ] = response.body.organizations + return organization + } catch (error) { + logToSentry(error) + console.log('Error loading organization:', error) + } +} + +async function fetchOrganization(organizationID, locale, env) { + const organization = await fetchOrganizationData(organizationID, env) + if (!organization) return null + + const translation = await fetchTranslations({ + translated_id: organizationID, + translated_type: 'organization', + fallback: organization?.primary_language, + language: locale, + env + }) + + return { + ...organization, + strings: translation?.strings || null + } +} + +export default fetchOrganization diff --git a/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.spec.js b/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.spec.js new file mode 100644 index 0000000000..6e8cece53c --- /dev/null +++ b/packages/app-project/src/helpers/fetchOrganization/fetchOrganization.spec.js @@ -0,0 +1,86 @@ +import { expect } from 'chai' +import nock from 'nock' + +import fetchOrganization from './fetchOrganization' + +describe('Helpers > fetchOrganization', function () { + const ORGANIZATION = { + id: '456', + display_name: 'Test Organization', + listed: true, + primary_language: 'en', + title: 'Test Organization', + } + + const TRANSLATIONS = [ + { + language: 'en', + strings: { + title: 'Test Organization' + } + }, + { + language: 'fr', + strings: { + title: 'traduction française' + } + } + ] + + before(function () { + nock('https://panoptes-staging.zooniverse.org/api') + .persist() + .get('/organizations') + .query(true) + .reply(200, { + organizations: [ORGANIZATION] + }) + .get('/translations') + .query(true) + .reply(200, { + translations: TRANSLATIONS + }) + }) + + after(function () { + nock.cleanAll() + }) + + it('should provide the expected result', async function () { + const result = await fetchOrganization('456', 'en', 'staging') + + expect(result).to.deep.equal({ + ...ORGANIZATION, + strings: TRANSLATIONS[0].strings + }) + }) + + describe('with an existing translation', function () { + it('should return the translated organization', async function () { + const result = await fetchOrganization('456', 'fr', 'staging') + + expect(result).to.deep.equal({ + ...ORGANIZATION, + strings: TRANSLATIONS[1].strings + }) + }) + }) + + describe('with an error', function () { + it('should return null', async function () { + const mockError = new Error('API is down') + const scope = nock('https://panoptes-staging.zooniverse.org/api') + .get('/organizations') + .query(true) + .replyWithError(mockError) + .get('/translations') + .query(true) + .reply(200, { + translations: TRANSLATIONS + }) + const result = await fetchOrganization('456', 'en', 'staging') + + expect(result).to.be.null + }) + }) +}) diff --git a/packages/app-project/src/helpers/fetchOrganization/index.js b/packages/app-project/src/helpers/fetchOrganization/index.js new file mode 100644 index 0000000000..985d336ead --- /dev/null +++ b/packages/app-project/src/helpers/fetchOrganization/index.js @@ -0,0 +1 @@ +export { default } from './fetchOrganization' diff --git a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js index 28098f0335..961b988cc6 100644 --- a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js +++ b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js @@ -9,7 +9,7 @@ const HOSTS = { export default async function getDefaultPageProps({ locale, params }) { const { props: staticProps } = await getStaticPageProps({ locale, params }) - const { project, notFound, title, workflowID, workflows } = staticProps + const { project, notFound, title, workflowID, workflows, organization } = staticProps const host = HOSTS[environment] || 'https://localhost:3000' /* snapshot for store hydration in the browser @@ -32,5 +32,9 @@ export default async function getDefaultPageProps({ locale, params }) { props.workflowID = workflowID } + if (organization) { + props.organization = organization + } + return { notFound, props } } diff --git a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.js b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.js index e64845972f..dd9178eacb 100644 --- a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.js +++ b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.js @@ -1,7 +1,8 @@ -import { panoptes } from '@zooniverse/panoptes-js' import { applySnapshot, getSnapshot } from 'mobx-state-tree' import notFoundError from '@helpers/notFoundError' + +import fetchOrganization from '@helpers/fetchOrganization' import fetchProjectData from '@helpers/fetchProjectData' import fetchTranslations from '@helpers/fetchTranslations' import fetchWorkflowsHelper from '@helpers/fetchWorkflowsHelper' @@ -72,6 +73,7 @@ export default async function getStaticPageProps({ locale, params }) { Fetch the active project workflows */ const workflows = await fetchWorkflowsHelper(language, project.links.active_workflows, workflowID, workflowOrder, env) + const props = { project: { ...project, @@ -79,10 +81,19 @@ export default async function getStaticPageProps({ locale, params }) { }, workflows } - + if (workflowID) { props.workflowID = workflowID } + /* + Fetch the organization linked to the project + */ + + if (project.links.organization) { + const organization = await fetchOrganization(project.links.organization, locale, env) + props.organization = organization + } + return { props } } diff --git a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js index d984a55897..e36f0802eb 100644 --- a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js +++ b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js @@ -36,6 +36,36 @@ describe('Helpers > getStaticPageProps', function () { } } + const PROJECT_WITH_ORGANIZATION = { + id: '3', + primary_language: 'en', + slug: 'test-owner/test-project-with-organization', + links: { + active_workflows: ['1'], + organization: '1', + workflows: ['1', '2'] + } + } + + const PROJECT_WITH_NOT_LISTED_ORGANIZATION = { + id: '4', + primary_language: 'en', + slug: 'test-owner/test-project-with-not-listed-organization', + links: { + active_workflows: ['1'], + organization: '2', + workflows: ['1', '2'] + } + } + + const ORGANIZATION = { + id: '1', + display_name: 'Test Organization', + listed: true, + primary_language: 'en', + title: 'Test Organization', + } + const TRANSLATION = { translated_id: 1, language: 'en', @@ -93,10 +123,31 @@ describe('Helpers > getStaticPageProps', function () { projects: [GROUPED_PROJECT] }) .get('/projects') + .query(query => query.slug === 'test-owner/test-project-with-organization') + .reply(200, { + projects: [PROJECT_WITH_ORGANIZATION] + }) + .get('/projects') + .query(query => query.slug === 'test-owner/test-project-with-not-listed-organization') + .reply(200, { + projects: [PROJECT_WITH_NOT_LISTED_ORGANIZATION] + }) + .get('/projects') .query(query => query.slug === 'test-owner/test-wrong-project') .reply(200, { projects: [] }) + .get('/organizations') + .query(query => query.id === '1') + .reply(200, { + organizations: [ORGANIZATION] + }) + .get('/organizations') + .query(query => query.id === '2') + .replyWithError({ + statusCode: 404, + message: 'Organization not found' + }) .get('/translations') .query(query => { return query.translated_type === 'project' @@ -107,6 +158,24 @@ describe('Helpers > getStaticPageProps', function () { translations: [TRANSLATION] }) .get('/translations') + .query(query => { + return query.translated_type === 'project' + && query.translated_id === '3' + && query.language === 'en' + }) + .reply(200, { + translations: [TRANSLATION] + }) + .get('/translations') + .query(query => { + return query.translated_type === 'project' + && query.translated_id === '4' + && query.language === 'en' + }) + .reply(200, { + translations: [TRANSLATION] + }) + .get('/translations') .query(query => { return query.translated_type === 'workflow' && query.translated_id === '1' @@ -133,6 +202,13 @@ describe('Helpers > getStaticPageProps', function () { .reply(200, { translations: [TRANSLATION, GROUPED_TRANSLATION] }) + .get('/translations') + .query(query => { + return query.translated_type === 'organization' + }) + .reply(200, { + translations: [TRANSLATION] + }) .get('/workflows') .query(query => query.id === '1') .reply(200, { @@ -242,6 +318,42 @@ describe('Helpers > getStaticPageProps', function () { }) }) + describe('with a valid project slug, linked listed organization', function () { + let props + + before(async function () { + const params = { + panoptesEnv: 'staging', + owner: 'test-owner', + project: 'test-project-with-organization' + } + const response = await getStaticPageProps({ params }) + props = response.props + }) + + it('should return the project\'s organization', async function () { + expect(props.organization).to.deep.equal({ ...ORGANIZATION, strings: { display_name: 'Foo' } }) + }) + }) + + describe('with a valid project slug, linked not listed organization', function () { + let props + + before(async function () { + const params = { + panoptesEnv: 'staging', + owner: 'test-owner', + project: 'test-project-with-not-listed-organization' + } + const response = await getStaticPageProps({ params }) + props = response.props + }) + + it('should return the project\'s organization as null', async function () { + expect(props.organization).to.be.null() + }) + }) + describe('with an invalid project slug', function () { let props @@ -375,6 +487,42 @@ describe('Helpers > getStaticPageProps', function () { }) }) + describe('with a valid project slug, linked listed organization', function () { + let props + + before(async function () { + const params = { + panoptesEnv: 'production', + owner: 'test-owner', + project: 'test-project-with-organization' + } + const response = await getStaticPageProps({ params }) + props = response.props + }) + + it('should return the project\'s organization', async function () { + expect(props.organization).to.deep.equal({ ...ORGANIZATION, strings: { display_name: 'Foo' } }) + }) + }) + + describe('with a valid project slug, linked not listed organization', function () { + let props + + before(async function () { + const params = { + panoptesEnv: 'production', + owner: 'test-owner', + project: 'test-project-with-not-listed-organization' + } + const response = await getStaticPageProps({ params }) + props = response.props + }) + + it('should return the project\'s organization as null', async function () { + expect(props.organization).to.be.null() + }) + }) + describe('with an invalid project slug', function () { let props diff --git a/packages/app-project/src/helpers/index.js b/packages/app-project/src/helpers/index.js index 7ed8cb0213..76d02bd97c 100644 --- a/packages/app-project/src/helpers/index.js +++ b/packages/app-project/src/helpers/index.js @@ -1,4 +1,5 @@ export { default as addQueryParams } from './addQueryParams' +export { default as fetchOrganization } from './fetchOrganization' export { default as fetchProjectData } from './fetchProjectData' export { default as fetchProjectPage } from './fetchProjectPage' export { default as fetchSubjectSets } from './fetchSubjectSets' diff --git a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js index 30323db7e5..c7cd959d12 100644 --- a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js +++ b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js @@ -48,8 +48,11 @@ function useStores() { } function ProjectHomePage ({ + organization, workflows }) { + console.log('ProjectHomePage organization.strings.title', organization?.strings?.title); + const { inBeta } = useStores() const { adminMode, toggleAdmin } = useAdminMode() const router = useRouter() @@ -125,11 +128,15 @@ function ProjectHomePage ({ ProjectHomePage.defaultProps = { inBeta: false, + organization: {}, workflows: [] } ProjectHomePage.propTypes = { inBeta: bool, + organization: shape({ + id: string + }), workflows: arrayOf(shape({ id: string.isRequired })) From 52695d5f8a2cc0465c453356d8f6ddd0dc2220f5 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Fri, 5 May 2023 10:31:24 -0500 Subject: [PATCH 02/12] Add OrganizationLink to ProjectHeader --- .../public/locales/en/components.json | 1 + .../components/ProjectHeader/ProjectHeader.js | 25 ++++++- .../ProjectHeader/ProjectHeader.spec.js | 23 +++++- .../ProjectHeader/ProjectHeader.stories.js | 42 +++++++++++ .../OrganizationLink/OrganizationLink.js | 73 +++++++++++++++++++ .../components/OrganizationLink/index.js | 1 + .../ProjectHeader/components/index.js | 1 + .../ProjectHomePage/ProjectHomePage.js | 12 ++- 8 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js create mode 100644 packages/app-project/src/components/ProjectHeader/components/OrganizationLink/index.js diff --git a/packages/app-project/public/locales/en/components.json b/packages/app-project/public/locales/en/components.json index 9aff3680de..1c70d56c11 100644 --- a/packages/app-project/public/locales/en/components.json +++ b/packages/app-project/public/locales/en/components.json @@ -105,6 +105,7 @@ "collect": "Collect", "recents": "Recents", "admin": "Admin page", + "organization": "From the organization:", "ApprovedIcon": { "title": "Zooniverse Approved" }, diff --git a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js index da5bf918d0..c8bd0da683 100644 --- a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js +++ b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js @@ -1,6 +1,6 @@ import { Box } from 'grommet' import { observer } from 'mobx-react' -import { bool, string } from 'prop-types' +import { bool, shape, string } from 'prop-types' import styled from 'styled-components' import { useResizeDetector } from 'react-resize-detector' @@ -10,6 +10,7 @@ import { Background, DropdownNav, LocaleSwitcher, + OrganizationLink, Nav, ProjectTitle, UnderReviewLabel @@ -22,6 +23,7 @@ const StyledBox = styled(Box)` function ProjectHeader({ adminMode, + organization, className = '' }) { const { width, height, ref } = useResizeDetector({ @@ -33,7 +35,6 @@ function ProjectHeader({ inBeta, title } = useStores() - const hasTranslations = availableLocales?.length > 1 const maxColumnWidth = hasTranslations ? 1000 : 900 const isNarrow = width < maxColumnWidth @@ -47,11 +48,21 @@ function ProjectHeader({ return ( + {organization?.id ? ( + + ) : null} ProjectHeader', function () { - let languageButton, navMenu, projectAvatar, projectBackground, projectTitle + let languageButton, navMenu, projectAvatar, projectBackground, projectTitle, organizationLink before(function () { nock('https://talk-staging.zooniverse.org') @@ -40,6 +41,7 @@ describe('Component > ProjectHeader', function () { projectTitle = screen.getByRole('heading', { level: 1, name: 'Snapshot Serengeti' }) navMenu = screen.getByRole('navigation', { name: 'ProjectHeader.ProjectNav.ariaLabel' }) languageButton = screen.queryByRole('button', { name: 'ProjectHeader.LocaleSwitcher.label'}) + organizationLink = screen.queryByRole('link', { name: 'Snapshot Safari' }) }) it('should display the project title', function () { @@ -61,6 +63,10 @@ describe('Component > ProjectHeader', function () { it('should not show the language menu button', function () { expect(languageButton).to.be.null() }) + + it('should not show the organization link', function () { + expect(organizationLink).to.be.null() + }) }) describe('when not logged in', function () { @@ -165,4 +171,17 @@ describe('Component > ProjectHeader', function () { expect(languageButton).to.exist() }) }) + + describe('with an organization', function () { + let organizationLink + + before(function () { + render() + organizationLink = screen.getByRole('link', { name: 'Snapshot Safari' }) + }) + + it('should show the organization link', async function () { + expect(organizationLink).to.exist() + }) + }) }) diff --git a/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js b/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js index acf7a8bafa..9f747fd8f7 100644 --- a/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js +++ b/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js @@ -17,6 +17,13 @@ const mockRouter = { } } +const ORGANIZATION = { + id: '1', + listed: true, + slug: 'zooniverse/snapshot-safari', + title: 'Snapshot Safari' +} + export default { title: 'Project App / Shared / Project Header', component: ProjectHeader @@ -302,3 +309,38 @@ MultipleLanguages.args = { } } } + +export function OrganizationLink({ project }) { + const snapshot = { project } + applySnapshot(OrganizationLink.store, snapshot) + return ( + + + + + + ) +} +OrganizationLink.store = initStore(true) +OrganizationLink.args = { + adminMode: false, + className: '', + project: { + avatar: { + src: 'https://panoptes-uploads.zooniverse.org/project_avatar/442e8392-6c46-4481-8ba3-11c6613fba56.jpeg' + }, + background: { + src: 'https://panoptes-uploads.zooniverse.org/project_background/7a3c6210-f97d-4f40-9ab4-8da30772ee01.jpeg' + }, + configuration: { + languages: ['en'] + }, + slug: 'zooniverse/snapshot-serengeti', + strings: { + display_name: 'Snapshot Serengeti' + }, + links: { + active_workflows: ['1'] + } + } +} diff --git a/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js new file mode 100644 index 0000000000..a5f02a1b5c --- /dev/null +++ b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js @@ -0,0 +1,73 @@ +import { Anchor, Box } from 'grommet' +import { string } from 'prop-types' +import styled from 'styled-components' +import { useTranslation } from 'next-i18next' +import { SpacedText } from '@zooniverse/react-components' + +const StyledBox = styled(Box)` + position: relative; +` + +/** + Link text styles +*/ +const StyledSpacedText = styled(SpacedText)` + text-shadow: 0 2px 2px rgba(0, 0, 0, 0.22); +` + +/** + Link styles +*/ +const StyledAnchor = styled(Anchor)` + border-bottom: 3px solid transparent; + white-space: nowrap; + + &:hover { + text-decoration: none; + border-bottom: 3px solid white; + } +` + +function OrganizationLink({ + slug = '', + title = '' +}) { + const { t } = useTranslation('components') + + return ( + + + {t('ProjectHeader.organization')} + + + + {title} + + + + ) +} + +OrganizationLink.propTypes = { + /** The organization slug */ + slug: string, + /** The organization name */ + title: string +} +export default OrganizationLink diff --git a/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/index.js b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/index.js new file mode 100644 index 0000000000..530da8714e --- /dev/null +++ b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/index.js @@ -0,0 +1 @@ +export { default } from './OrganizationLink' diff --git a/packages/app-project/src/components/ProjectHeader/components/index.js b/packages/app-project/src/components/ProjectHeader/components/index.js index 990f5356b9..5145d9a63f 100644 --- a/packages/app-project/src/components/ProjectHeader/components/index.js +++ b/packages/app-project/src/components/ProjectHeader/components/index.js @@ -4,5 +4,6 @@ export { default as Background } from './Background' export { default as DropdownNav } from './DropdownNav' export { default as LocaleSwitcher } from './LocaleSwitcher' export { default as Nav } from './Nav' +export { default as OrganizationLink } from './OrganizationLink' export { default as ProjectTitle } from './ProjectTitle' export { default as UnderReviewLabel } from './UnderReviewLabel' diff --git a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js index c7cd959d12..9a67bb30dc 100644 --- a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js +++ b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js @@ -51,8 +51,6 @@ function ProjectHomePage ({ organization, workflows }) { - console.log('ProjectHomePage organization.strings.title', organization?.strings?.title); - const { inBeta } = useStores() const { adminMode, toggleAdmin } = useAdminMode() const router = useRouter() @@ -73,7 +71,10 @@ function ProjectHomePage ({
- +
@@ -91,7 +92,10 @@ function ProjectHomePage ({
- +
From 7ade431a0d637a51ac703f9169c696571dda21f7 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Fri, 5 May 2023 16:59:31 -0500 Subject: [PATCH 03/12] Add OrganizationLink to Hero component --- .../ProjectHomePage/ProjectHomePage.js | 11 +++++- .../ProjectHomePage/components/Hero/Hero.js | 9 ++++- .../components/Hero/Hero.stories.js | 19 ++++++++++ .../components/NarrowLayout/NarrowLayout.js | 23 +++++++---- .../OrganizationLink/OrganizationLink.js | 38 +++++++++++++++++++ .../OrganizationLink/OrganizationLink.spec.js | 13 +++++++ .../Hero/components/OrganizationLink/index.js | 1 + .../Hero/components/WideLayout/WideLayout.js | 29 ++++++++++---- 8 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.js create mode 100644 packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.spec.js create mode 100644 packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/index.js diff --git a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js index 9a67bb30dc..7ed6ef28d2 100644 --- a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js +++ b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js @@ -77,7 +77,10 @@ function ProjectHomePage ({ /> - + @@ -99,7 +102,11 @@ function ProjectHomePage ({ - +
- : + ? + : } Hero.propTypes = { /** SSR flag to render either the wide or narrow screen layout. */ isWide: bool, + /** The project's organization. */ + organization: shape({ + id: string + }), /** An array of workflows for the workflow menu. */ workflows: arrayOf(shape({ id: string.isRequired diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js index 4856b534b4..97f537b486 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js @@ -106,3 +106,22 @@ SmallScreen.parameters = { defaultViewport: 'iphone5' } } + +const ORGANIZATION = { + id: '1', + listed: true, + slug: 'brbcornell/nest-quest-go', + title: 'Nest Quest Go' +} + +export function WithOrganization({ isWide }) { + return ( + + + + ) +} diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js index 7e13519c96..03e4fb4ecb 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js @@ -3,12 +3,14 @@ import { arrayOf, shape, string } from 'prop-types' import Background from '../Background' import Introduction from '../Introduction' +import OrganizationLink from '../OrganizationLink' import WorkflowSelector from '@shared/components/WorkflowSelector' import ContentBox from '@shared/components/ContentBox' -function NarrowLayout (props) { - const { workflows } = props - +function NarrowLayout ({ + organization = {}, + workflows = [] +}) { return ( + {organization?.id ? ( + + ) : null} @@ -32,11 +40,12 @@ function NarrowLayout (props) { ) } -NarrowLayout.defaultProps = { - workflows: [] -} - NarrowLayout.propTypes = { + organization: shape({ + id: string, + slug: string, + title: string + }), workflows: arrayOf(shape({ id: string.isRequired })) diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.js new file mode 100644 index 0000000000..eaeceb40fa --- /dev/null +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.js @@ -0,0 +1,38 @@ +import { Anchor, Box } from 'grommet' +import { useTranslation } from 'next-i18next' +import { string } from 'prop-types' +import { SpacedText } from '@zooniverse/react-components' + +function OrganizationLink({ + slug = '', + title = '' +}) { + const { t } = useTranslation('components') + + return ( + + + {t('ProjectHeader.organization')} + + + + {title} + + + + ) +} + +OrganizationLink.propTypes = { + slug: string, + title: string +} + +export default OrganizationLink diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.spec.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.spec.js new file mode 100644 index 0000000000..33102ecbd1 --- /dev/null +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/OrganizationLink.spec.js @@ -0,0 +1,13 @@ +import { render, screen } from '@testing-library/react' + +import OrganizationLink from './OrganizationLink' + +const SLUG = 'zooniverse/test-organization' +const TITLE = 'Test Organization' + +describe('Component > Hero > OrganizationLink', function () { + it('should render a link to the organization page', function () { + render() + expect(screen.getByRole('link', { name: TITLE })).to.exist() + }) +}) diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/index.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/index.js new file mode 100644 index 0000000000..530da8714e --- /dev/null +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/OrganizationLink/index.js @@ -0,0 +1 @@ +export { default } from './OrganizationLink' diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/WideLayout/WideLayout.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/WideLayout/WideLayout.js index 68148c422e..b5744cfd43 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/WideLayout/WideLayout.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/WideLayout/WideLayout.js @@ -4,6 +4,7 @@ import styled from 'styled-components' import Background from '../Background' import Introduction from '../Introduction' +import OrganizationLink from '../OrganizationLink' import WorkflowSelector from '@shared/components/WorkflowSelector' import ContentBox from '@shared/components/ContentBox' @@ -15,11 +16,16 @@ const StyledContentBox = styled(ContentBox)` border-color: transparent; ` -function WideLayout (props) { - const { workflows } = props - +function WideLayout ({ + organization = {}, + workflows = [] +}) { return ( - + @@ -30,6 +36,12 @@ function WideLayout (props) { width='38%' > + {organization?.id ? ( + + ) : null} @@ -38,11 +50,12 @@ function WideLayout (props) { ) } -WideLayout.defaultProps = { - workflows: [] -} - WideLayout.propTypes = { + organization: shape({ + id: string, + slug: string, + title: string + }), workflows: arrayOf(shape({ id: string.isRequired })) From c0d883a34e6c0882b7234a29ebe5f67f84313534 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Tue, 9 May 2023 12:34:15 -0500 Subject: [PATCH 04/12] Fix organization link href Co-authored-by: Jim O'Donnell --- .../components/OrganizationLink/OrganizationLink.js | 2 +- .../Hero/components/OrganizationLink/OrganizationLink.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js index a5f02a1b5c..23197a3e74 100644 --- a/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js +++ b/packages/app-project/src/components/ProjectHeader/components/OrganizationLink/OrganizationLink.js @@ -51,7 +51,7 @@ function OrganizationLink({ {t('ProjectHeader.organization')} Date: Thu, 1 Jun 2023 15:28:33 -0500 Subject: [PATCH 05/12] Fix Hero story WithOrganization --- .../ProjectHomePage/components/Hero/Hero.stories.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js index 97f537b486..54d93168f9 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js @@ -116,12 +116,10 @@ const ORGANIZATION = { export function WithOrganization({ isWide }) { return ( - - - + ) } From 9c2e0e8bb154836b3c04c07b9ab6e09cf5c1e5f4 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Thu, 1 Jun 2023 15:34:32 -0500 Subject: [PATCH 06/12] Add organization link to About pages ProjectHeader --- .../screens/ProjectAboutPage/ProjectAboutPage.js | 11 ++++++++++- .../ProjectAboutPage/ProjectAboutPageConnector.js | 12 +++++++++++- .../components/StandardLayout/StandardLayout.js | 15 ++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.js b/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.js index 8864fb9da5..a00e142286 100644 --- a/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.js +++ b/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.js @@ -28,6 +28,7 @@ function ProjectAboutPage ({ aboutNavLinks, aboutPageData = {}, inBeta = false, + organization, projectDisplayName, screenSize, teamArray = [], @@ -47,7 +48,10 @@ function ProjectAboutPage ({ // note that for future additional locales, CSS property :lang is available to format strings return ( - + { +const ProjectAboutPageConnector = ({ + organization, + pageType, + teamArray +}) => { const returnDefaultContent = () => { const pageTitle = pageType === 'science_case' ? 'research' : pageType @@ -57,6 +61,7 @@ const ProjectAboutPageConnector = ({ pageType, teamArray }) => { aboutNavLinks={aboutNavLinks} aboutPageData={aboutPageData} inBeta={inBeta} + organization={organization} projectDisplayName={display_name} teamArray={teamArray} /> @@ -65,6 +70,11 @@ const ProjectAboutPageConnector = ({ pageType, teamArray }) => { ProjectAboutPageConnector.propTypes = { inBeta: bool, + organization: shape({ + id: string, + slug: string, + title: string + }), pageType: string, teamArray: arrayOf( shape({ diff --git a/packages/app-project/src/shared/components/StandardLayout/StandardLayout.js b/packages/app-project/src/shared/components/StandardLayout/StandardLayout.js index 64d4f44f1e..3669c10a88 100644 --- a/packages/app-project/src/shared/components/StandardLayout/StandardLayout.js +++ b/packages/app-project/src/shared/components/StandardLayout/StandardLayout.js @@ -1,4 +1,4 @@ -import { node } from 'prop-types' +import { node, shape, string } from 'prop-types' import { Box } from 'grommet' import { observer, MobXProviderContext } from 'mobx-react' import { useContext } from 'react' @@ -31,6 +31,7 @@ function useStores() { function StandardLayout ({ children, + organization, }) { const { inBeta } = useStores() const { adminMode, toggleAdmin } = useAdminMode() @@ -47,7 +48,10 @@ function StandardLayout ({
- +
{children} @@ -60,7 +64,12 @@ function StandardLayout ({ } StandardLayout.propTypes = { - children: node + children: node, + organization: shape({ + id: string, + slug: string, + title: string + }) } export default observer(StandardLayout) From 385855f69e40279b6a901074c41a05aa7844f457 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Thu, 1 Jun 2023 15:38:20 -0500 Subject: [PATCH 07/12] Add organization link to ClassifyPage for ProjectHeader --- .../src/screens/ClassifyPage/ClassifyPage.js | 9 +++++++-- .../src/screens/ProjectHomePage/ProjectHomePage.js | 11 ++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index 7dd05629ed..e9fd7790c1 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -24,6 +24,7 @@ function ClassifyPage({ addToCollection, appLoadingState, onSubjectReset, + organization, screenSize, subjectID, subjectSetID, @@ -72,8 +73,7 @@ function ClassifyPage({ } return ( - - + Date: Tue, 6 Jun 2023 23:30:33 -0500 Subject: [PATCH 08/12] Add Organization store --- packages/app-project/stores/Organization.js | 19 +++++++ .../app-project/stores/Organization.spec.js | 50 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 packages/app-project/stores/Organization.js create mode 100644 packages/app-project/stores/Organization.spec.js diff --git a/packages/app-project/stores/Organization.js b/packages/app-project/stores/Organization.js new file mode 100644 index 0000000000..706e12f7a2 --- /dev/null +++ b/packages/app-project/stores/Organization.js @@ -0,0 +1,19 @@ +import { types } from 'mobx-state-tree' + +import numberString from './types/numberString' + +const Organization = types + .model('Organization', { + id: types.maybeNull(numberString), + primary_language: types.optional(types.string, 'en'), + slug: types.optional(types.string, ''), + strings: types.frozen({}), + }) + + .views(self => ({ + get title () { + return self.strings?.title + }, + })) + +export default Organization diff --git a/packages/app-project/stores/Organization.spec.js b/packages/app-project/stores/Organization.spec.js new file mode 100644 index 0000000000..b442bad688 --- /dev/null +++ b/packages/app-project/stores/Organization.spec.js @@ -0,0 +1,50 @@ +import { expect } from 'chai' +import Store from './Store' +import placeholderEnv from './helpers/placeholderEnv' + +describe('Stores > Organization', function () { + let organizationStore + let rootStore + + it('should exist', function () { + rootStore = Store.create({ + organization: { + strings: { + title: 'some text' + } + } + }, placeholderEnv) + organizationStore = rootStore.organization + expect(organizationStore).to.be.ok() + }) + + describe('default model properties', function () { + before(function () { + rootStore = Store.create({ + organization: { + strings: { + title: 'some text' + } + } + }, placeholderEnv) + organizationStore = rootStore.organization + }) + + after(function () { + rootStore = null + organizationStore = null + }) + + it('should have an `id` property', function () { + expect(organizationStore.id).to.be.null() + }) + + it('should have a `slug` property', function () { + expect(organizationStore.slug).to.equal('') + }) + + it('should have a `title` property', function () { + expect(organizationStore.title).to.be.a('string') + }) + }) +}) From 01b2052cf7c178ec284e368107a87e236e1c4d60 Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Tue, 6 Jun 2023 23:38:04 -0500 Subject: [PATCH 09/12] Refactor Store and useStore for organization store --- .../src/components/ProjectHeader/hooks/useStores.js | 6 ++++++ .../src/helpers/getDefaultPageProps/getDefaultPageProps.js | 3 ++- .../src/screens/ClassifyPage/ClassifyPageContainer.spec.js | 3 +++ .../src/screens/ProjectAboutPage/ProjectAboutPage.spec.js | 1 + packages/app-project/stores/Store.js | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/app-project/src/components/ProjectHeader/hooks/useStores.js b/packages/app-project/src/components/ProjectHeader/hooks/useStores.js index b544568246..a6dbf75aa5 100644 --- a/packages/app-project/src/components/ProjectHeader/hooks/useStores.js +++ b/packages/app-project/src/components/ProjectHeader/hooks/useStores.js @@ -3,6 +3,10 @@ import { MobXProviderContext } from 'mobx-react' function storeMapper(store) { const { + organization: { + slug: organizationSlug, + title: organizationTitle + }, project: { configuration: { languages @@ -32,6 +36,8 @@ function storeMapper(store) { inBeta, isAdmin, isLoggedIn, + organizationSlug, + organizationTitle, slug, title } diff --git a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js index 961b988cc6..e0015c4693 100644 --- a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js +++ b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js @@ -33,7 +33,8 @@ export default async function getDefaultPageProps({ locale, params }) { } if (organization) { - props.organization = organization + props.organization = organization // TODO: delete this in favor of organization store + props.initialState.organization = organization } return { notFound, props } diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPageContainer.spec.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPageContainer.spec.js index 6a517c745e..77154a4c24 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPageContainer.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPageContainer.spec.js @@ -44,6 +44,7 @@ describe('Component > ClassifyPageContainer', function () { }] const mockStore = { + organization: {}, project: { avatar: { src: '' @@ -144,6 +145,7 @@ describe('Component > ClassifyPageContainer', function () { }] const mockStore = { + organization: {}, project: { avatar: { src: '' @@ -348,6 +350,7 @@ describe('Component > ClassifyPageContainer', function () { describe('when the project is not workflow assignment enabled', function () { let routerStub const mockStore = { + organization: {}, project: { avatar: { src: '' diff --git a/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.spec.js b/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.spec.js index cb67efbbb0..7cf1c1fb5d 100644 --- a/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.spec.js +++ b/packages/app-project/src/screens/ProjectAboutPage/ProjectAboutPage.spec.js @@ -16,6 +16,7 @@ import i18n from '@test/i18n-for-tests' describe('Component > ProjectAboutPage & Connector', function () { const mockStore = { + organization: {}, project: { about_pages: [ { diff --git a/packages/app-project/stores/Store.js b/packages/app-project/stores/Store.js index 5211e16b12..a2995c2f81 100644 --- a/packages/app-project/stores/Store.js +++ b/packages/app-project/stores/Store.js @@ -3,12 +3,14 @@ import { addDisposer, addMiddleware, getEnv, onAction, types } from 'mobx-state- import { autorun } from 'mobx' import logToSentry from '../src/helpers/logger/logToSentry.js' +import Organization from './Organization' import Project from './Project' import UI from './UI' import User from './User' const Store = types .model('Store', { + organization: types.optional(Organization, {}), project: types.optional(Project, {}), ui: types.optional(UI, {}), user: types.optional(User, {}) From abb900e351140b95d84c87900915ace02b2e0d8a Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Thu, 8 Jun 2023 15:56:49 -0500 Subject: [PATCH 10/12] Refactor components to use organization store --- .../components/ProjectHeader/ProjectHeader.js | 23 ++- .../ProjectHeader/ProjectHeader.stories.js | 14 +- .../getDefaultPageProps.js | 1 - .../src/screens/ClassifyPage/ClassifyPage.js | 8 +- .../ProjectAboutPage/ProjectAboutPage.js | 11 +- .../ProjectAboutPageConnector.js | 12 +- .../ProjectHomePage/ProjectHomePage.js | 24 +-- .../ProjectHomePage/components/Hero/Hero.js | 9 +- .../components/Hero/Hero.stories.js | 140 +++++++++--------- .../components/NarrowLayout/NarrowLayout.js | 24 ++- .../Hero/components/WideLayout/WideLayout.js | 26 ++-- .../StandardLayout/StandardLayout.js | 17 +-- 12 files changed, 136 insertions(+), 173 deletions(-) diff --git a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js index c8bd0da683..6ccc62614b 100644 --- a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js +++ b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js @@ -1,6 +1,6 @@ import { Box } from 'grommet' import { observer } from 'mobx-react' -import { bool, shape, string } from 'prop-types' +import { bool, string } from 'prop-types' import styled from 'styled-components' import { useResizeDetector } from 'react-resize-detector' @@ -22,8 +22,7 @@ const StyledBox = styled(Box)` ` function ProjectHeader({ - adminMode, - organization, + adminMode = false, className = '' }) { const { width, height, ref } = useResizeDetector({ @@ -33,6 +32,8 @@ function ProjectHeader({ const { availableLocales, inBeta, + organizationTitle, + organizationSlug, title } = useStores() const hasTranslations = availableLocales?.length > 1 @@ -48,10 +49,10 @@ function ProjectHeader({ return ( - {organization?.id ? ( + {(organizationTitle && !useDropdownNav) ? ( ) : null} @@ -109,13 +110,7 @@ ProjectHeader.propTypes = { /** Zooniverse admin mode */ adminMode: bool, /** Optional CSS classes */ - className: string, - /** Project organization */ - organization: shape({ - id: string, - slug: string, - title: string - }) + className: string } export default observer(ProjectHeader) diff --git a/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js b/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js index 9f747fd8f7..116fe4d5e9 100644 --- a/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js +++ b/packages/app-project/src/components/ProjectHeader/ProjectHeader.stories.js @@ -21,7 +21,9 @@ const ORGANIZATION = { id: '1', listed: true, slug: 'zooniverse/snapshot-safari', - title: 'Snapshot Safari' + strings: { + title: 'Snapshot Safari' + } } export default { @@ -310,13 +312,16 @@ MultipleLanguages.args = { } } -export function OrganizationLink({ project }) { - const snapshot = { project } +export function OrganizationLink({ organization, project }) { + const snapshot = { + organization, + project + } applySnapshot(OrganizationLink.store, snapshot) return ( - + ) @@ -325,6 +330,7 @@ OrganizationLink.store = initStore(true) OrganizationLink.args = { adminMode: false, className: '', + organization: ORGANIZATION, project: { avatar: { src: 'https://panoptes-uploads.zooniverse.org/project_avatar/442e8392-6c46-4481-8ba3-11c6613fba56.jpeg' diff --git a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js index e0015c4693..ff3b0f2970 100644 --- a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js +++ b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.js @@ -33,7 +33,6 @@ export default async function getDefaultPageProps({ locale, params }) { } if (organization) { - props.organization = organization // TODO: delete this in favor of organization store props.initialState.organization = organization } diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index e9fd7790c1..57ef43eb34 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -24,7 +24,6 @@ function ClassifyPage({ addToCollection, appLoadingState, onSubjectReset, - organization, screenSize, subjectID, subjectSetID, @@ -73,7 +72,7 @@ function ClassifyPage({ } return ( - + + { +const ProjectAboutPageConnector = ({ pageType, teamArray }) => { const returnDefaultContent = () => { const pageTitle = pageType === 'science_case' ? 'research' : pageType @@ -61,7 +57,6 @@ const ProjectAboutPageConnector = ({ aboutNavLinks={aboutNavLinks} aboutPageData={aboutPageData} inBeta={inBeta} - organization={organization} projectDisplayName={display_name} teamArray={teamArray} /> @@ -70,11 +65,6 @@ const ProjectAboutPageConnector = ({ ProjectAboutPageConnector.propTypes = { inBeta: bool, - organization: shape({ - id: string, - slug: string, - title: string - }), pageType: string, teamArray: arrayOf( shape({ diff --git a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js index b3898cc407..c5b89d92ae 100644 --- a/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js +++ b/packages/app-project/src/screens/ProjectHomePage/ProjectHomePage.js @@ -1,9 +1,9 @@ import { Box, Grid } from 'grommet' import { observer, MobXProviderContext } from 'mobx-react' -import { arrayOf, bool, shape, string } from 'prop-types' +import { arrayOf, shape, string } from 'prop-types' import { useContext } from 'react' import styled from 'styled-components' -import { AdminCheckbox, ZooFooter } from '@zooniverse/react-components' +import { ZooFooter } from '@zooniverse/react-components' import { useRouter } from 'next/router' import { useAdminMode } from '@hooks' @@ -48,7 +48,6 @@ function useStores() { } function ProjectHomePage ({ - organization = {}, workflows = [] }) { const { inBeta } = useStores() @@ -71,16 +70,10 @@ function ProjectHomePage ({
- +
- + @@ -95,16 +88,12 @@ function ProjectHomePage ({
- +
@@ -138,9 +127,6 @@ function ProjectHomePage ({ } ProjectHomePage.propTypes = { - organization: shape({ - id: string - }), workflows: arrayOf(shape({ id: string.isRequired })) diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.js index 27fa1c0645..c7f29ddce7 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.js @@ -8,21 +8,16 @@ import NarrowLayout from './components/NarrowLayout' */ function Hero ({ isWide = false, - organization = {}, workflows = [] }) { return isWide - ? - : + ? + : } Hero.propTypes = { /** SSR flag to render either the wide or narrow screen layout. */ isWide: bool, - /** The project's organization. */ - organization: shape({ - id: string - }), /** An array of workflows for the workflow menu. */ workflows: arrayOf(shape({ id: string.isRequired diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js index 1a856c01e4..af73002335 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.stories.js @@ -7,62 +7,37 @@ import Router from 'next/router' import Store from '@stores/Store' import Hero from './' -function RouterMock({ children }) { - const mockRouter = { - locale: 'en', - push: () => {}, - prefetch: () => new Promise((resolve, reject) => {}), - query: { - owner: 'test-owner', - project: 'test-project' - } +const ORGANIZATION = { + id: '1', + listed: true, + slug: 'brbcornell/nest-quest-go', + strings: { + title: 'Nest Quest Go' } - - Router.router = mockRouter - - return ( - - {children} - - ) } -const snapshot = { - project: { - background: { - src: 'https://panoptes-uploads.zooniverse.org/production/project_background/260e68fd-d3ec-4a94-bb32-43ff91d5579a.jpeg' - }, - slug: 'brbcornell/nest-quest-go-western-bluebirds', - strings: { - description: 'Learn about and help document the wonders of nesting Western Bluebirds.', - display_name: 'Nest Quest Go: Western Bluebirds', - introduction: 'Bluebirds introduction', - workflow_description: 'Choose your own adventure! There are many ways to engage with this project!' - } +const PROJECT = { + background: { + src: 'https://panoptes-uploads.zooniverse.org/production/project_background/260e68fd-d3ec-4a94-bb32-43ff91d5579a.jpeg' }, - user: { - loadingState: asyncStates.success, - personalization: { - projectPreferences: { - loadingState: asyncStates.success - } - } + slug: 'brbcornell/nest-quest-go-western-bluebirds', + strings: { + description: 'Learn about and help document the wonders of nesting Western Bluebirds.', + display_name: 'Nest Quest Go: Western Bluebirds', + introduction: 'Bluebirds introduction', + workflow_description: 'Choose your own adventure! There are many ways to engage with this project!' } } -const store = Store.create(snapshot) - -function MockProjectContext(Story) { - return ( - - - - - - - - ) +const USER = { + loadingState: asyncStates.success, + personalization: { + projectPreferences: { + loadingState: asyncStates.success + } + } } + const WORKFLOWS = [ { completeness: 0.65, @@ -84,42 +59,67 @@ const WORKFLOWS = [ } ] +function RouterMock({ children }) { + const mockRouter = { + locale: 'en', + push: () => {}, + prefetch: () => new Promise((resolve, reject) => {}), + query: { + owner: 'test-owner', + project: 'test-project' + } + } + + Router.router = mockRouter + + return ( + + {children} + + ) +} + export default { title: 'Project App / Screens / Project Home / Hero', component: Hero, args: { isWide: true - }, - decorators: [MockProjectContext] -} - -export function Default({ isWide }) { - return + } } -export function SmallScreen() { - return +const snapshot = { + project: PROJECT, + user: USER } +const store = Store.create(snapshot) -SmallScreen.parameters = { - viewport: { - defaultViewport: 'iphone5' - } +export function Default({ isWide }) { + return ( + + + + + + + + ) } -const ORGANIZATION = { - id: '1', - listed: true, - slug: 'brbcornell/nest-quest-go', - title: 'Nest Quest Go' +const snapshotWithOrganization = { + organization: ORGANIZATION, + project: PROJECT, + user: USER } +const storeWithOrganization = Store.create(snapshotWithOrganization) export function WithOrganization({ isWide }) { return ( - + + + + + + + ) } diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js index f2a896a52d..7679e046f1 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/components/NarrowLayout/NarrowLayout.js @@ -1,5 +1,7 @@ import { Box, Grid } from 'grommet' +import { observer, MobXProviderContext } from 'mobx-react' import { arrayOf, shape, string } from 'prop-types' +import { useContext } from 'react' import Background from '../Background' import Introduction from '../Introduction' @@ -7,10 +9,21 @@ import OrganizationLink from '../OrganizationLink' import WorkflowSelector from '@shared/components/WorkflowSelector' import ContentBox from '@shared/components/ContentBox' +function useStores() { + const { store } = useContext(MobXProviderContext) + const { + organization + } = store + return { + organization + } +} + function NarrowLayout ({ - organization = {}, workflows = [] }) { + const { organization } = useStores() + return ( ) : null} - {organization?.id ? ( + {organization.id ? ( ) : null}
- +
{children} @@ -64,12 +60,7 @@ function StandardLayout ({ } StandardLayout.propTypes = { - children: node, - organization: shape({ - id: string, - slug: string, - title: string - }) + children: node } export default observer(StandardLayout) From 217be8f17c4d57f92dcaabf1c0777f879397b04e Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Fri, 9 Jun 2023 12:36:51 -0500 Subject: [PATCH 11/12] Add organization related tests to Hero and initStore tests --- .../components/Hero/Hero.spec.js | 13 +++++++++- packages/app-project/stores/initStore.js | 3 ++- packages/app-project/stores/initStore.spec.js | 24 ++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.spec.js b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.spec.js index 734566b536..401fd4c51b 100644 --- a/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.spec.js +++ b/packages/app-project/src/screens/ProjectHomePage/components/Hero/Hero.spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai' import { composeStory } from '@storybook/react' import { render, screen } from '@testing-library/react' -import Meta, { Default } from './Hero.stories.js' +import Meta, { Default, WithOrganization } from './Hero.stories.js' describe('Component > Hero', function () { let img, title, description, link @@ -31,4 +31,15 @@ describe('Component > Hero', function () { it('should show a link to the about page', function () { expect(link).to.be.ok() }) + + describe('with an organization', function () { + let organizationLink + const WithOrganizationStory = composeStory(WithOrganization, Meta) + + it('should show a link to the organization', function () { + render() + organizationLink = screen.getByRole('link', { name: 'Nest Quest Go' }) + expect(organizationLink).to.be.ok() + }) + }) }) diff --git a/packages/app-project/stores/initStore.js b/packages/app-project/stores/initStore.js index 80951ade51..a2a0da86cf 100644 --- a/packages/app-project/stores/initStore.js +++ b/packages/app-project/stores/initStore.js @@ -65,8 +65,9 @@ function initStore (isServer, snapshot = null, client = defaultClient) { Only apply store state that was generated on the server. TODO: won't this overwrite local changes to the UI store? */ - const { project } = snapshot + const { organization, project } = snapshot applySnapshot(store.project, project) + if (organization?.id) applySnapshot(store.organization, organization) } return store diff --git a/packages/app-project/stores/initStore.spec.js b/packages/app-project/stores/initStore.spec.js index 4511d1dc01..c4e59a4d44 100644 --- a/packages/app-project/stores/initStore.spec.js +++ b/packages/app-project/stores/initStore.spec.js @@ -67,7 +67,7 @@ describe('Stores > initStore', function () { } } cleanStore() - const store=initStore(false, pageProps) + const store = initStore(false, pageProps) const user = User.create({ id: '12345', display_name: 'test user', @@ -117,4 +117,26 @@ describe('Stores > initStore', function () { expect(store.user.loadingState).to.equal(asyncStates.success) }) }) + + describe('with an organization', function () { + it('should contain an organization store', function () { + const store = initStore() + expect(store.organization).to.be.ok() + }) + + it('should apply a snapshot when provided', function () { + const snapshot = { + organization: { + id: '67890', + slug: 'test-user/test-org', + strings: { + title: 'Test Organization' + } + } + } + const store = initStore(true, snapshot) + expect(store.organization.slug).to.equal('test-user/test-org') + expect(store.organization.title).to.equal('Test Organization') + }) + }) }) From 32588c1184dfb171a5ee31d06d7101e92a0a7d3e Mon Sep 17 00:00:00 2001 From: Mark Bouslog Date: Fri, 9 Jun 2023 16:23:36 -0500 Subject: [PATCH 12/12] Add organization link to DropdownNav --- .../components/ProjectHeader/ProjectHeader.js | 2 + .../components/DropdownNav/DropdownNav.js | 53 +++++++++++++++---- .../DropdownNav/DropdownNav.spec.js | 29 +++++++++- .../DropdownNav/DropdownNav.stories.js | 36 +++++++++++++ 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js index 6ccc62614b..c5e311dbb0 100644 --- a/packages/app-project/src/components/ProjectHeader/ProjectHeader.js +++ b/packages/app-project/src/components/ProjectHeader/ProjectHeader.js @@ -97,6 +97,8 @@ function ProjectHeader({ ) : (