mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #9622 from overleaf/mf-add-portal-templates-2
Add institution templates to the react version of the new project dropdown GitOrigin-RevId: 32bf0b1b559ea3da744430902cc016e7c2a918d9
This commit is contained in:
parent
c42cedbcdc
commit
7608d37c0a
6 changed files with 175 additions and 60 deletions
|
@ -1,4 +1,5 @@
|
|||
const _ = require('lodash')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const ProjectHelper = require('./ProjectHelper')
|
||||
const ProjectGetter = require('./ProjectGetter')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
|
@ -19,6 +20,17 @@ const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
|||
const UserPrimaryEmailCheckHandler = require('../User/UserPrimaryEmailCheckHandler')
|
||||
const UserController = require('../User/UserController')
|
||||
|
||||
/** @typedef {import("./types").GetProjectsRequest} GetProjectsRequest */
|
||||
/** @typedef {import("./types").GetProjectsResponse} GetProjectsResponse */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Project} Project */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Filters} Filters */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Page} Page */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Sort} Sort */
|
||||
/** @typedef {import("./types").AllUsersProjects} AllUsersProjects */
|
||||
/** @typedef {import("./types").MongoProject} MongoProject */
|
||||
|
||||
/** @typedef {import("../Tags/types").Tag} Tag */
|
||||
|
||||
const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
|
||||
if (!affiliation.institution) return false
|
||||
|
||||
|
@ -38,16 +50,29 @@ const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
|
|||
return false
|
||||
}
|
||||
|
||||
/** @typedef {import("./types").GetProjectsRequest} GetProjectsRequest */
|
||||
/** @typedef {import("./types").GetProjectsResponse} GetProjectsResponse */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Project} Project */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Filters} Filters */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Page} Page */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Sort} Sort */
|
||||
/** @typedef {import("./types").AllUsersProjects} AllUsersProjects */
|
||||
/** @typedef {import("./types").MongoProject} MongoProject */
|
||||
const _buildPortalTemplatesList = affiliations => {
|
||||
if (affiliations == null) {
|
||||
affiliations = []
|
||||
}
|
||||
|
||||
/** @typedef {import("../Tags/types").Tag} Tag */
|
||||
const portalTemplates = []
|
||||
const uniqueAffiliations = _.uniqBy(affiliations, 'institution.id')
|
||||
for (const aff of uniqueAffiliations) {
|
||||
const hasSlug = aff.portal?.slug
|
||||
const hasTemplates = aff.portal?.templates_count > 0
|
||||
|
||||
if (hasSlug && hasTemplates) {
|
||||
const portalPath = aff.institution.isUniversity ? '/edu/' : '/org/'
|
||||
const portalTemplateURL = Settings.siteUrl + portalPath + aff.portal?.slug
|
||||
|
||||
portalTemplates.push({
|
||||
name: aff.institution.name,
|
||||
url: portalTemplateURL,
|
||||
})
|
||||
}
|
||||
}
|
||||
return portalTemplates
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("express").Request} req
|
||||
|
@ -158,6 +183,8 @@ async function projectListReactPage(req, res, next) {
|
|||
return result
|
||||
})
|
||||
|
||||
const portalTemplates = _buildPortalTemplatesList(userAffiliations)
|
||||
|
||||
const { allInReconfirmNotificationPeriods } = userEmailsData
|
||||
|
||||
const notifications =
|
||||
|
@ -258,6 +285,7 @@ async function projectListReactPage(req, res, next) {
|
|||
allInReconfirmNotificationPeriods,
|
||||
survey,
|
||||
tags,
|
||||
portalTemplates,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ block append meta
|
|||
meta(name="ol-reconfirmedViaSAML" content=reconfirmedViaSAML)
|
||||
meta(name="ol-survey" data-type="json" content=survey)
|
||||
meta(name="ol-tags" data-type="json" content=tags)
|
||||
meta(name="ol-portalTemplates" data-type="json" content=portalTemplates)
|
||||
|
||||
block content
|
||||
main.content.content-alt.project-list-react#project-list-root
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"importing_and_merging_changes_in_github": "",
|
||||
"in_order_to_match_institutional_metadata_2": "",
|
||||
"in_order_to_match_institutional_metadata_associated": "",
|
||||
"institution": "",
|
||||
"institution_acct_successfully_linked_2": "",
|
||||
"institution_and_role": "",
|
||||
"institutional_leavers_survey_notification": "",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useState } from 'react'
|
|||
import { Dropdown, MenuItem } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ExposedSettings } from '../../../../../types/exposed-settings'
|
||||
import type { PortalTemplate } from '../../../../../types/portal-template'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import NewProjectButtonModal, {
|
||||
|
@ -24,6 +25,7 @@ function NewProjectButton({
|
|||
const { templateLinks } = getMeta('ol-ExposedSettings') as ExposedSettings
|
||||
const [modal, setModal] =
|
||||
useState<Nullable<NewProjectButtonModalVariant>>(null)
|
||||
const portalTemplates = getMeta('ol-portalTemplates') as PortalTemplate[]
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -48,6 +50,22 @@ function NewProjectButton({
|
|||
<MenuItem onClick={() => setModal('import_from_github')}>
|
||||
{t('import_from_github')}
|
||||
</MenuItem>
|
||||
{portalTemplates?.length > 0 ? (
|
||||
<>
|
||||
<MenuItem divider />
|
||||
<MenuItem header>
|
||||
{`${t('institution')} ${t('templates')}`}
|
||||
</MenuItem>
|
||||
{portalTemplates.map((portalTemplate, index) => (
|
||||
<MenuItem
|
||||
key={`portal-template-${index}`}
|
||||
href={`${portalTemplate.url}#templates`}
|
||||
>
|
||||
{portalTemplate.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
<MenuItem divider />
|
||||
<MenuItem header>{t('templates')}</MenuItem>
|
||||
{templateLinks.map((templateLink, index) => (
|
||||
|
|
|
@ -3,70 +3,133 @@ import { expect } from 'chai'
|
|||
import NewProjectButton from '../../../../../frontend/js/features/project-list/components/new-project-button'
|
||||
|
||||
describe('<NewProjectButton />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
templateLinks: [
|
||||
{
|
||||
name: 'Academic Journal',
|
||||
url: '/gallery/tagged/academic-journal',
|
||||
},
|
||||
{
|
||||
name: 'View All',
|
||||
url: '/latex/templates',
|
||||
},
|
||||
],
|
||||
describe('for every user (affiliated and non-affiliated)', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
templateLinks: [
|
||||
{
|
||||
name: 'Academic Journal',
|
||||
url: '/gallery/tagged/academic-journal',
|
||||
},
|
||||
{
|
||||
name: 'View All',
|
||||
url: '/latex/templates',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
render(<NewProjectButton id="test" />)
|
||||
|
||||
const newProjectButton = screen.getByRole('button', {
|
||||
name: 'New Project',
|
||||
})
|
||||
fireEvent.click(newProjectButton)
|
||||
})
|
||||
|
||||
render(<NewProjectButton id="test" />)
|
||||
|
||||
const newProjectButton = screen.getByRole('button', {
|
||||
name: 'New Project',
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
|
||||
it('shows the correct dropdown menu', function () {
|
||||
// static menu
|
||||
screen.getByText('Blank Project')
|
||||
screen.getByText('Example Project')
|
||||
screen.getByText('Upload Project')
|
||||
screen.getByText('Import from GitHub')
|
||||
|
||||
// static text
|
||||
screen.getByText('Templates')
|
||||
|
||||
// dynamic menu based on templateLinks
|
||||
screen.getByText('Academic Journal')
|
||||
screen.getByText('View All')
|
||||
})
|
||||
|
||||
it('open new project modal when clicking at Blank Project', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
|
||||
screen.getByPlaceholderText('Project Name')
|
||||
})
|
||||
|
||||
it('open new project modal when clicking at Example Project', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Example Project' }))
|
||||
|
||||
screen.getByPlaceholderText('Project Name')
|
||||
})
|
||||
|
||||
it('close the new project modal when clicking at the top right "x" button', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.be.null
|
||||
})
|
||||
|
||||
it('close the new project modal when clicking at the Cancel button', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.be.null
|
||||
})
|
||||
fireEvent.click(newProjectButton)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
describe('for affiliated user with custom templates', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
templateLinks: [
|
||||
{
|
||||
name: 'Academic Journal',
|
||||
url: '/gallery/tagged/academic-journal',
|
||||
},
|
||||
{
|
||||
name: 'View All',
|
||||
url: '/latex/templates',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
it('opens a dropdown', function () {
|
||||
// static menu
|
||||
screen.getByText('Blank Project')
|
||||
screen.getByText('Example Project')
|
||||
screen.getByText('Upload Project')
|
||||
screen.getByText('Import from GitHub')
|
||||
window.metaAttributesCache.set('ol-portalTemplates', [
|
||||
{
|
||||
name: 'Affiliation 1',
|
||||
url: '/edu/test-new-template',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
// static text
|
||||
screen.getByText('Templates')
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
|
||||
// dynamic menu based on templateLinks
|
||||
screen.getByText('Academic Journal')
|
||||
screen.getByText('View All')
|
||||
})
|
||||
it('shows the correct dropdown menu', function () {
|
||||
render(<NewProjectButton id="test" />)
|
||||
|
||||
it('open new project modal when clicking at Blank Project', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
const newProjectButton = screen.getByRole('button', {
|
||||
name: 'New Project',
|
||||
})
|
||||
|
||||
screen.getByPlaceholderText('Project Name')
|
||||
})
|
||||
fireEvent.click(newProjectButton)
|
||||
// static menu
|
||||
screen.getByText('Blank Project')
|
||||
screen.getByText('Example Project')
|
||||
screen.getByText('Upload Project')
|
||||
screen.getByText('Import from GitHub')
|
||||
|
||||
it('open new project modal when clicking at Example Project', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Example Project' }))
|
||||
// static text for institution templates
|
||||
screen.getByText('Institution Templates')
|
||||
|
||||
screen.getByPlaceholderText('Project Name')
|
||||
})
|
||||
// dynamic menu based on portalTemplates
|
||||
const affiliationTemplate = screen.getByRole('menuitem', {
|
||||
name: 'Affiliation 1',
|
||||
})
|
||||
expect(affiliationTemplate.getAttribute('href')).to.equal(
|
||||
'/edu/test-new-template#templates'
|
||||
)
|
||||
|
||||
it('close the new project modal when clicking at the top right "x" button', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
|
||||
// static text
|
||||
screen.getByText('Templates')
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.be.null
|
||||
})
|
||||
|
||||
it('close the new project modal when clicking at the Cancel button', function () {
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.be.null
|
||||
// dynamic menu based on templateLinks
|
||||
screen.getByText('Academic Journal')
|
||||
screen.getByText('View All')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
4
services/web/types/portal-template.ts
Normal file
4
services/web/types/portal-template.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type PortalTemplate = {
|
||||
name: string
|
||||
url: string
|
||||
}
|
Loading…
Reference in a new issue