Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel
- Edit Terms Integration.
- With the addition of the new runtime configuration approach, we now support dynamic configuration for languages. If more than one language is configured, the Language Switcher will be shown in the header to allow users to change the language.
- Added Notifications tab in Account Page
- Added runtime configuration options for homepage branding and support link.
- Added an environment variable to docker-compose-dev.yml to hide the OIDC client used in the SPA from the JSF frontend: DATAVERSE_AUTH_OIDC_HIDDEN_JSF: 1

### Changed
Expand Down
10 changes: 10 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export default defineConfig({
{ code: 'es', name: 'Español' }
],
defaultLanguage: 'en',
branding: {
dataverseName: 'Dataverse'
},
homepage: {
supportUrl: 'https://support.dataverse.harvard.edu/'
},
footer: {
copyrightHolder: 'The President & Fellows of Harvard College',
privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy'
},
codeCoverage: {
exclude: ['tests/**/*.*', '**/ErrorPage.tsx']
}
Expand Down
14 changes: 13 additions & 1 deletion public/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,17 @@ window.__APP_CONFIG__ = {
{ code: 'es', name: 'Español' }
],
// Default language code from the list above
defaultLanguage: 'en'
defaultLanguage: 'en',
// Optional branding values for homepage/footer text
branding: {
// Used in homepage strings such as "{{dataverseName}} is a repository..."
dataverseName: 'Harvard Dataverse'
},
homepage: {
supportUrl: 'https://support.dataverse.harvard.edu/'
},
footer: {
copyrightHolder: 'The President & Fellows of Harvard College',
privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy'
}
}
2 changes: 1 addition & 1 deletion public/locales/en/footer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"copyright": "Copyright © {{year}}, The President & Fellows of Harvard College | ",
"copyright": "Copyright © {{year}}, {{copyrightHolder}}",
"privacyPolicy": "Privacy Policy",
"poweredBy": "Powered by"
}
4 changes: 2 additions & 2 deletions public/locales/en/homepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"usage": {
"datasets": {
"title": "Deposit and share your data. Get academic credit.",
"content": "Harvard Dataverse is a repository for research data. Deposit data and code here.",
"content": "{{dataverseName}} is a repository for research data. Deposit data and code here.",
"text_button": "Add a dataset"
},
"collections": {
Expand All @@ -29,7 +29,7 @@
"text_button": "Add a collection"
},
"general": {
"title": "Publishing your data is easy on Harvard Dataverse!",
"title": "Publishing your data is easy on {{dataverseName}}!",
"content": "Learn about getting started creating your own dataverse repository here.",
"text_button": "Getting started"
}
Expand Down
2 changes: 1 addition & 1 deletion public/locales/es/footer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"copyright": "Copyright © {{year}}, El Presidente y Miembros de la Universidad de Harvard | ",
"copyright": "Copyright © {{year}}, {{copyrightHolder}}",
"privacyPolicy": "Política de privacidad",
"poweredBy": "Desarrollado por"
}
4 changes: 2 additions & 2 deletions public/locales/es/homepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"usage": {
"datasets": {
"title": "Deposita y comparte tus datos. Obtén crédito académico.",
"content": "Harvard Dataverse es un repositorio para datos de investigación. Deposita aquí tus datos y código.",
"content": "{{dataverseName}} es un repositorio para datos de investigación. Deposita aquí tus datos y código.",
"text_button": "Agregar un dataset"
},
"collections": {
Expand All @@ -29,7 +29,7 @@
"text_button": "Agregar una colección"
},
"general": {
"title": "¡Publicar tus datos es fácil en Harvard Dataverse!",
"title": "¡Publicar tus datos es fácil en {{dataverseName}}!",
"content": "Aprende cómo comenzar a crear tu propio repositorio Dataverse aquí.",
"text_button": "Comenzar"
}
Expand Down
18 changes: 17 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,23 @@ const AppConfigSchema = z.object({
code: z.string(),
name: z.string()
})
)
),
branding: z
.object({
dataverseName: z.string().optional()
})
.optional(),
homepage: z
.object({
supportUrl: z.url().optional()
})
.optional(),
footer: z
.object({
copyrightHolder: z.string().optional(),
privacyPolicyUrl: z.url().optional()
})
.optional()
})

export type AppConfig = z.infer<typeof AppConfigSchema>
Expand Down
11 changes: 8 additions & 3 deletions src/sections/homepage/usage/Usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import { useTranslation } from 'react-i18next'
import { Card, Col, Row, Stack } from '@iqss/dataverse-design-system'
import { BoxArrowUpRight, Plus } from 'react-bootstrap-icons'
import { RouteWithParams } from '@/sections/Route.enum'
import { requireAppConfig } from '@/config'
import styles from './Usage.module.scss'

interface UsageProps {
collectionId: string
}

const DEFAULT_SUPPORT_URL = 'https://guides.dataverse.org/en/latest/user/index.html'

export const Usage = ({ collectionId }: UsageProps) => {
const { t } = useTranslation('homepage', { keyPrefix: 'usage' })
const appConfig = requireAppConfig()
const dataverseName = appConfig.branding?.dataverseName ?? 'Dataverse'
const supportUrl = appConfig.homepage?.supportUrl ?? DEFAULT_SUPPORT_URL

return (
<Row>
Expand Down Expand Up @@ -51,15 +57,14 @@ export const Usage = ({ collectionId }: UsageProps) => {
</Card>
</Col>

{/* TODO: This column might be only for Harvard Dataverse */}
<Col xs={12} lg={4} className={styles.column_card}>
<Card className={styles.card}>
<Card.Body className={styles.card_body}>
<h5>{t('general.title')}</h5>
<h5>{t('general.title', { dataverseName })}</h5>
<p className="small text-muted">{t('general.content')}</p>
<footer className={styles.footer_wrapper}>
<a
href="https://support.dataverse.harvard.edu/"
href={supportUrl}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary btn-sm">
Expand Down
22 changes: 14 additions & 8 deletions src/sections/layout/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useTranslation } from 'react-i18next'
import { Container, Row, Col } from '@iqss/dataverse-design-system'
import dataverseProjectLogo from '@/assets/dataverse-project-logo.svg'
import { requireAppConfig } from '@/config'
import { DataverseInfoRepository } from '../../../info/domain/repositories/DataverseInfoRepository'
import { useDataverseVersion } from './useDataverseVersion'
import dataverseProjectLogo from '@/assets/dataverse-project-logo.svg'
import styles from './Footer.module.scss'

interface FooterProps {
Expand All @@ -13,20 +14,25 @@ export function Footer({ dataverseInfoRepository }: FooterProps) {
const { t } = useTranslation('footer')
const { dataverseVersion } = useDataverseVersion(dataverseInfoRepository)
const currentYear = new Date().getFullYear().toString()
const appConfig = requireAppConfig()
const copyrightHolder = appConfig.footer?.copyrightHolder ?? 'Dataverse Project'
const privacyPolicyUrl = appConfig.footer?.privacyPolicyUrl

return (
<footer className={styles.container}>
<Container>
<Row>
<Col sm={8}>
<em className={styles.copyright}>
{t('copyright', { year: currentYear })}
<a
href="https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy"
rel="noreferrer"
target="_blank">
{t('privacyPolicy')}
</a>
{t('copyright', { year: currentYear, copyrightHolder })}
{privacyPolicyUrl && (
<>
{' | '}
<a href={privacyPolicyUrl} rel="noreferrer" target="_blank">
{t('privacyPolicy')}
</a>
</>
)}
</em>
</Col>
<Col sm={4}>
Expand Down
68 changes: 68 additions & 0 deletions tests/component/sections/homepage/Usage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Usage } from '@/sections/homepage/usage/Usage'
import { applyTestAppConfig } from '@tests/support/bootstrapAppConfig'
import type { AppConfig } from '@/config'

describe('Usage', () => {
const collectionId = 'test-collection-id'
const defaultHomepageEnv = Cypress.env('homepage') as AppConfig['homepage']
const defaultBrandingEnv = Cypress.env('branding') as AppConfig['branding']

afterEach(() => {
Cypress.env('homepage', defaultHomepageEnv)
Cypress.env('branding', defaultBrandingEnv)
applyTestAppConfig()
})

it('renders usage cards and links to create dataset and collection routes', () => {
cy.customMount(<Usage collectionId={collectionId} />)

cy.findByRole('heading', {
name: 'Deposit and share your data. Get academic credit.'
}).should('be.visible')
cy.findByRole('heading', {
name: 'Organize datasets and gather metrics in your own repository.'
}).should('be.visible')

cy.findByRole('link', { name: 'Add a dataset' }).should(
'have.attr',
'href',
`/datasets/${collectionId}/create`
)
cy.findByRole('link', { name: 'Add a collection' }).should(
'have.attr',
'href',
`/collections/${collectionId}/create`
)
})

it('uses app config branding and support URL for the general card', () => {
const supportUrl = 'https://example.org/help'

Cypress.env('branding', { dataverseName: 'Testverse' })
Cypress.env('homepage', { supportUrl })
applyTestAppConfig()

cy.customMount(<Usage collectionId={collectionId} />)

cy.findByRole('heading', {
name: 'Publishing your data is easy on Testverse!'
}).should('be.visible')
cy.findByRole('link', { name: 'Getting started' })
.should('have.attr', 'href', supportUrl)
.and('have.attr', 'target', '_blank')
.and('have.attr', 'rel', 'noreferrer noopener')
})

it('falls back to the default support URL when config support URL is missing', () => {
Cypress.env('homepage', {})
applyTestAppConfig()

cy.customMount(<Usage collectionId={collectionId} />)

cy.findByRole('link', { name: 'Getting started' }).should(
'have.attr',
'href',
'https://guides.dataverse.org/en/latest/user/index.html'
)
})
})
23 changes: 21 additions & 2 deletions tests/component/sections/layout/footer/Footer.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import { DataverseVersionMother } from '../../../info/domain/models/DataverseVer
import { DataverseInfoRepository } from '../../../../../src/info/domain/repositories/DataverseInfoRepository'
import { FooterMother } from './FooterMother'
import { Footer } from '../../../../../src/sections/layout/footer/Footer'
import { applyTestAppConfig } from '../../../../support/bootstrapAppConfig'
import type { AppConfig } from '@/config'
import type { DatasetMetadataExportFormats } from '@/info/domain/models/DatasetMetadataExportFormats'
import type { TermsOfUse } from '@/info/domain/models/TermsOfUse'
import type { ZipDownloadLimit } from '@/settings/domain/models/ZipDownloadLimit'
import type { Setting } from '@/settings/domain/models/Setting'

describe('Footer component', () => {
const sandbox: SinonSandbox = createSandbox()
const testVersion = DataverseVersionMother.create()
const currentYear = new Date().getFullYear().toString()
const defaultFooterEnv = Cypress.env('footer') as AppConfig['footer']

it('should render footer content', () => {
cy.customMount(FooterMother.withDataverseVersion(sandbox, testVersion))
Expand All @@ -20,7 +27,15 @@ describe('Footer component', () => {

it('should call dataverseInfoRepository.getVersion on mount', () => {
const dataverseInfoRepository: DataverseInfoRepository = {
getVersion: cy.stub().resolves(testVersion)
getVersion: cy.stub().resolves(testVersion),
getTermsOfUse: cy.stub().resolves({} as TermsOfUse),
getZipDownloadLimit: cy.stub().resolves({} as Setting<ZipDownloadLimit>),
getMaxEmbargoDurationInMonths: cy.stub().resolves({} as Setting<number>),
getHasPublicStore: cy.stub().resolves({} as Setting<boolean>),
getExternalStatusesAllowed: cy.stub().resolves({} as Setting<string[]>),
getAvailableDatasetMetadataExportFormats: cy
.stub()
.resolves({} as DatasetMetadataExportFormats)
}

cy.customMount(<Footer dataverseInfoRepository={dataverseInfoRepository} />)
Expand All @@ -30,8 +45,12 @@ describe('Footer component', () => {
})

it('should open privacy policy link in new tab', () => {
Cypress.env('footer', defaultFooterEnv)
applyTestAppConfig()
cy.customMount(FooterMother.withDataverseVersion(sandbox))

cy.findByText('Privacy Policy').should('exist')
cy.findByText('Privacy Policy')
.should('have.attr', 'href', defaultFooterEnv?.privacyPolicyUrl)
.and('have.attr', 'target', '_blank')
})
})
16 changes: 15 additions & 1 deletion tests/support/bootstrapAppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ declare global {
}

function buildTestConfig(): AppConfig {
const branding = (Cypress.env('branding') as AppConfig['branding']) ?? {
dataverseName: 'Dataverse'
}
const homepage = (Cypress.env('homepage') as AppConfig['homepage']) ?? {
supportUrl: 'https://support.dataverse.harvard.edu/'
}
const footer = (Cypress.env('footer') as AppConfig['footer']) ?? {
copyrightHolder: 'The President & Fellows of Harvard College',
privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy'
}

return {
backendUrl: Cypress.env('backendUrl') as string,
oidc: {
Expand All @@ -20,7 +31,10 @@ function buildTestConfig(): AppConfig {
localStorageKeyPrefix: Cypress.env('oidcLocalStorageKeyPrefix') as string
},
languages: Cypress.env('languages') as { code: string; name: string }[],
defaultLanguage: Cypress.env('defaultLanguage') as string
defaultLanguage: Cypress.env('defaultLanguage') as string,
branding,
homepage,
footer
}
}

Expand Down
Loading