Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function FileNonTabularDownloadOptions({

return (
<DropdownButtonItem
data-testid={`download-original-file`}
href={downloadUrlOriginal}
disabled={ingestIsInProgress || (dataset && dataset.isLockedFromFileDownload)}>
{type.displayFormatIsUnknown
Expand Down
23 changes: 19 additions & 4 deletions src/sections/homepage/usage/Usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,33 @@ 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>
<Col xs={12} lg={4} className={styles.column_card}>
<Card className={styles.card}>
<Card.Body className={styles.card_body}>
<h5>{t('datasets.title')}</h5>
<p className="small text-muted">{t('datasets.content')}</p>
<p className="small text-muted">
{t('datasets.content', {
dataverseName,
interpolation: { escapeValue: false }
})}
</p>
<footer className={styles.footer_wrapper}>
<Link
to={RouteWithParams.CREATE_DATASET(collectionId)}
Expand Down Expand Up @@ -51,15 +62,19 @@ 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,
interpolation: { escapeValue: false }
})}
</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
26 changes: 18 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,29 @@ 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,
interpolation: { escapeValue: false }
})}
{privacyPolicyUrl && (
<>
{' | '}
<a href={privacyPolicyUrl} rel="noreferrer" target="_blank">
{t('privacyPolicy')}
</a>
</>
)}
</em>
</Col>
<Col sm={4}>
Expand Down
71 changes: 71 additions & 0 deletions tests/component/sections/homepage/Usage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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.findByText(
'Testverse is a repository for research data. Deposit data and code here.'
).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'
)
})
})
27 changes: 24 additions & 3 deletions tests/component/sections/layout/footer/Footer.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,41 @@ 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))

cy.contains(`Copyright © ${currentYear}`).should('exist')
cy.contains(`Copyright © ${currentYear}, The President & Fellows of Harvard College`).should(
'exist'
)
cy.findByText('Privacy Policy').should('exist')
cy.findByAltText('The Dataverse Project logo').should('exist')
cy.findByText(testVersion).should('exist')
})

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 +47,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')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const fileExpectedData = (id: number, datasetPid: string): Omit<File, 'hierarchy
name: 'blob',
datasetPersistentId: datasetPid,
datasetVersion: {
lastUpdateTime: dateNow,
labels: [
{ semanticMeaning: DatasetLabelSemanticMeaning.DATASET, value: DatasetLabelValue.DRAFT },
{
Expand Down
14 changes: 7 additions & 7 deletions tests/e2e-integration/shared/files/FileHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ export class FileHelper extends DataverseApiHelper {
.then((binary: string) => Cypress.Blob.binaryStringToBlob(binary, 'image/jpeg'))
}

static async download(id: number) {
cy.visit(`/file.xhtml?fileId=${id}`)
.get('#actionButtonBlock > div:nth-child(1) > div > button')
.click()
.get('#fileForm\\:j_idt274')
.click()
return Promise.resolve()
static download(id: number): Promise<void> {
return new Cypress.Promise((resolve) => {
cy.visit(`/spa/files?id=${id}`)
cy.get(`#action-button-access-file-${id}`).click()
cy.findByTestId(`download-original-file`).click()
cy.then(() => resolve())
})
}

static async addLabel(id: number, labels: FileLabel[]) {
Expand Down
Loading
Loading