From 4556675ad255c7248ad3996ce467ffa6f4a3bd33 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 25 Oct 2023 11:18:37 -0700 Subject: [PATCH] Merge pull request #15150 from overleaf/mf-file-view-tpr-module Move mendeley/zotero file-view UI to `tpr-webmodule` folder GitOrigin-RevId: af3cfe614fcf415d5842cf98dc2a42a3898ccd8b --- services/web/config/settings.defaults.js | 6 +- .../file-view/components/file-view-header.tsx | 23 +++- .../file-view-not-original-importer.tsx | 38 ------ .../components/file-view-refresh-button.tsx | 104 ++++++++++------- .../components/file-view-refresh-error.tsx | 99 +++++----------- .../features/file-view/types/binary-file.ts | 10 +- .../{ => file-view}/file-view.stories.jsx | 50 +------- .../frontend/stories/linked-file.stories.jsx | 25 ---- .../file-view-not-original-importer.test.tsx | 108 ------------------ .../file-view-refresh-button.test.jsx | 58 ---------- .../file-view-refresh-error.test.tsx | 108 ++++-------------- 11 files changed, 139 insertions(+), 490 deletions(-) delete mode 100644 services/web/frontend/js/features/file-view/components/file-view-not-original-importer.tsx rename services/web/frontend/stories/{ => file-view}/file-view.stories.jsx (81%) delete mode 100644 services/web/frontend/stories/linked-file.stories.jsx delete mode 100644 services/web/test/frontend/features/file-view/components/file-view-not-original-importer.test.tsx diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index e27b029895..f16b204bbb 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -827,8 +827,10 @@ module.exports = { createFileModes: [], gitBridge: [], publishModal: [], - tprLinkedFileInfo: [], - tprLinkedFileRefreshError: [], + tprFileViewInfo: [], + tprFileViewRefreshError: [], + tprFileViewRefreshButton: [], + tprFileViewNotOriginalImporter: [], contactUsModal: [], editorToolbarButtons: [], sourceEditorExtensions: [], diff --git a/services/web/frontend/js/features/file-view/components/file-view-header.tsx b/services/web/frontend/js/features/file-view/components/file-view-header.tsx index 5de43e79bd..aa2e0b2030 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-header.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-header.tsx @@ -12,11 +12,17 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac import { LinkedFileIcon } from './file-view-icons' import { BinaryFile, hasProvider, LinkedFile } from '../types/binary-file' import FileViewRefreshButton from './file-view-refresh-button' -import FileViewNotOriginalImporter from './file-view-not-original-importer' import FileViewRefreshError from './file-view-refresh-error' -const tprLinkedFileInfo = importOverleafModules('tprLinkedFileInfo') as { - import: { LinkedFileInfo: ElementType } +const tprFileViewInfo = importOverleafModules('tprFileViewInfo') as { + import: { TPRFileViewInfo: ElementType } + path: string +}[] + +const tprFileViewNotOriginalImporter = importOverleafModules( + 'tprFileViewNotOriginalImporter' +) as { + import: { TPRFileViewNotOriginalImporter: ElementType } path: string }[] @@ -79,8 +85,8 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
{file.linkedFileData && fileInfo} {file.linkedFileData && - tprLinkedFileInfo.map(({ import: { LinkedFileInfo }, path }) => ( - + tprFileViewInfo.map(({ import: { TPRFileViewInfo }, path }) => ( + ))} {file.linkedFileData && permissionsLevel !== 'readOnly' && ( @@ -95,7 +101,12 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {   {t('download')} - {file.linkedFileData && } + {file.linkedFileData && + tprFileViewNotOriginalImporter.map( + ({ import: { TPRFileViewNotOriginalImporter }, path }) => ( + + ) + )[0]} {refreshError && ( )} diff --git a/services/web/frontend/js/features/file-view/components/file-view-not-original-importer.tsx b/services/web/frontend/js/features/file-view/components/file-view-not-original-importer.tsx deleted file mode 100644 index a18b1c3a5d..0000000000 --- a/services/web/frontend/js/features/file-view/components/file-view-not-original-importer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { capitalize } from 'lodash' -import { useUserContext } from '@/shared/context/user-context' -import { BinaryFile, hasProvider } from '../types/binary-file' - -type FileViewNotOriginalImporterProps = { - file: BinaryFile -} - -export default function FileViewNotOriginalImporter({ - file, -}: FileViewNotOriginalImporterProps) { - const { t } = useTranslation() - const { id: userId } = useUserContext() - - const isMendeleyOrZotero = - hasProvider(file, 'mendeley') || hasProvider(file, 'zotero') - - if (!isMendeleyOrZotero) { - return null - } - - const isImporter = file.linkedFileData.importer_id === userId - - if (isImporter) { - return null - } - - return ( -
-
- {t('only_importer_can_refresh', { - provider: capitalize(file.linkedFileData.provider), - })} -
-
- ) -} diff --git a/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx b/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx index b3e4eee1e7..be6e68ccc0 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx @@ -1,73 +1,99 @@ import { type Dispatch, type SetStateAction, + type ElementType, useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import Icon from '@/shared/components/icon' import { postJSON } from '@/infrastructure/fetch-json' import { useProjectContext } from '@/shared/context/project-context' import useAbortController from '@/shared/hooks/use-abort-controller' -import { useUserContext } from '@/shared/context/user-context' -import { hasProvider, type BinaryFile } from '../types/binary-file' +import type { BinaryFile } from '../types/binary-file' import { Nullable } from '../../../../../types/utils' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' type FileViewRefreshButtonProps = { setRefreshError: Dispatch>> file: BinaryFile } +const tprFileViewRefreshButton = importOverleafModules( + 'tprFileViewRefreshButton' +) as { + import: { TPRFileViewRefreshButton: ElementType } + path: string +}[] + export default function FileViewRefreshButton({ setRefreshError, file, }: FileViewRefreshButtonProps) { - const { signal } = useAbortController() - const { t } = useTranslation() - const [refreshing, setRefreshing] = useState(false) const { _id: projectId } = useProjectContext() - const { id: userId } = useUserContext() + const { signal } = useAbortController() + const [refreshing, setRefreshing] = useState(false) - const isMendeleyOrZotero = - hasProvider(file, 'mendeley') || hasProvider(file, 'zotero') + const refreshFile = useCallback( + (isTPR: Nullable) => { + setRefreshing(true) + // Replacement of the file handled by the file tree + window.expectingLinkedFileRefreshedSocketFor = file.name + const body = { + shouldReindexReferences: isTPR || /\.bib$/.test(file.name), + } + postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, { + signal, + body, + }) + .then(() => { + setRefreshing(false) + }) + .catch(err => { + setRefreshing(false) + setRefreshError(err.data?.message || err.message) + }) + }, + [file, projectId, signal, setRefreshError] + ) - let isImporter - - if (isMendeleyOrZotero) { - isImporter = file.linkedFileData.importer_id === userId + if (tprFileViewRefreshButton.length > 0) { + return tprFileViewRefreshButton.map( + ({ import: { TPRFileViewRefreshButton }, path }) => ( + + ) + )[0] + } else { + return ( + + ) } +} - const buttonClickable = isMendeleyOrZotero ? isImporter : true +type FileViewRefreshButtonDefaultProps = { + refreshFile: (isTPR: Nullable) => void + refreshing: boolean +} - const refreshFile = useCallback(() => { - setRefreshing(true) - // Replacement of the file handled by the file tree - window.expectingLinkedFileRefreshedSocketFor = file.name - const body = { - shouldReindexReferences: isMendeleyOrZotero || /\.bib$/.test(file.name), - } - postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, { - signal, - body, - }) - .then(() => { - setRefreshing(false) - }) - .catch(err => { - setRefreshing(false) - setRefreshError(err.data?.message || err.message) - }) - }, [file, projectId, signal, setRefreshError, isMendeleyOrZotero]) +function FileViewRefreshButtonDefault({ + refreshFile, + refreshing, +}: FileViewRefreshButtonDefaultProps) { + const { t } = useTranslation() return ( -
- - ) -} -type FileViewDefaultRefreshErrorProps = { - refreshError: string -} - -function FileViewDefaultRefreshError({ - refreshError, -}: FileViewDefaultRefreshErrorProps) { - const { t } = useTranslation() - return ( -
- {t('access_denied')}: {refreshError} -
- ) + if (tprFileViewRefreshError.length > 0) { + return tprFileViewRefreshError.map( + ({ import: { TPRFileViewRefreshError }, path }) => ( + + ) + )[0] + } else { + return ( +
+
+
+ {t('access_denied')}: {refreshError} +
+
+ ) + } } diff --git a/services/web/frontend/js/features/file-view/types/binary-file.ts b/services/web/frontend/js/features/file-view/types/binary-file.ts index efed8be1d1..05c133bf37 100644 --- a/services/web/frontend/js/features/file-view/types/binary-file.ts +++ b/services/web/frontend/js/features/file-view/types/binary-file.ts @@ -1,16 +1,8 @@ -type LinkedFileData = { +export type LinkedFileData = { url: { provider: 'url' url: string } - zotero: { - provider: 'zotero' - importer_id: string - } - mendeley: { - provider: 'mendeley' - importer_id: string - } project_file: { provider: 'project_file' v1_source_doc_id?: string diff --git a/services/web/frontend/stories/file-view.stories.jsx b/services/web/frontend/stories/file-view/file-view.stories.jsx similarity index 81% rename from services/web/frontend/stories/file-view.stories.jsx rename to services/web/frontend/stories/file-view/file-view.stories.jsx index e79350cb7a..4bd9953356 100644 --- a/services/web/frontend/stories/file-view.stories.jsx +++ b/services/web/frontend/stories/file-view/file-view.stories.jsx @@ -1,6 +1,6 @@ -import FileView from '../js/features/file-view/components/file-view' -import useFetchMock from './hooks/use-fetch-mock' -import { ScopeDecorator } from './decorators/scope' +import FileView from '../../js/features/file-view/components/file-view' +import useFetchMock from '../hooks/use-fetch-mock' +import { ScopeDecorator } from '../decorators/scope' const bodies = { latex: `\\documentclass{article} @@ -161,50 +161,6 @@ ImageFile.args = { }, } -export const ThirdPartyReferenceFile = args => { - useFetchMock(fetchMock => - setupFetchMock(fetchMock).get( - 'express:/project/:project_id/file/:file_id', - { body: bodies.bibtex } - ) - ) - - return -} - -ThirdPartyReferenceFile.args = { - file: { - ...fileData, - name: 'references.bib', - linkedFileData: { - provider: 'zotero', - }, - }, -} - -export const ThirdPartyReferenceFileWithError = args => { - useFetchMock(fetchMock => - setupFetchMock(fetchMock).head( - 'express:/project/:project_id/file/:file_id', - { status: 500 }, - { overwriteRoutes: true } - ) - ) - return -} -ThirdPartyReferenceFileWithError.storyName = - 'Third Party Reference File (Error)' -ThirdPartyReferenceFileWithError.args = { - file: { - ...fileData, - id: '500500500500500500500500', - name: 'references.bib', - linkedFileData: { - provider: 'zotero', - }, - }, -} - export const TextFile = args => { useFetchMock(fetchMock => setupFetchMock(fetchMock).get( diff --git a/services/web/frontend/stories/linked-file.stories.jsx b/services/web/frontend/stories/linked-file.stories.jsx deleted file mode 100644 index 587c4e0e67..0000000000 --- a/services/web/frontend/stories/linked-file.stories.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { LinkedFileInfo } from '../../modules/tpr-webmodule/frontend/js/components/linked-file-info' - -export const MendeleyLinkedFile = args => { - return -} - -MendeleyLinkedFile.args = { - file: { - linkedFileData: { - provider: 'mendeley', - }, - }, -} - -export default { - title: 'Editor / LinkedFileInfo', - component: LinkedFileInfo, - args: { - file: { - id: 'file-id', - name: 'file.tex', - created: new Date(), - }, - }, -} diff --git a/services/web/test/frontend/features/file-view/components/file-view-not-original-importer.test.tsx b/services/web/test/frontend/features/file-view/components/file-view-not-original-importer.test.tsx deleted file mode 100644 index c5da1f3ead..0000000000 --- a/services/web/test/frontend/features/file-view/components/file-view-not-original-importer.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { screen } from '@testing-library/react' -import { expect } from 'chai' -import FileViewNotOriginalImporter from '@/features/file-view/components/file-view-not-original-importer' -import { BinaryFile } from '@/features/file-view/types/binary-file' -import { renderWithEditorContext } from '../../../helpers/render-with-context' -import { USER_ID } from '../../../helpers/editor-providers' - -describe('', function () { - describe('provider is not mendeley and zotero', function () { - it('does not show error if provider is not mendeley and zotero', function () { - const urlFile: BinaryFile<'url'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'url', - url: '/url/file.png', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'file.png', - type: 'file', - selected: true, - } - - renderWithEditorContext() - - const text = screen.queryByText( - /Only the person who originally imported this/ - ) - - expect(text).to.not.exist - }) - }) - - describe('provider is mendeley or zotero', function () { - it('does not show error if current user is the original importer of the file', function () { - const mendeleyFile: BinaryFile<'mendeley'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'mendeley', - importer_id: USER_ID, - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'references.bib', - type: 'file', - selected: true, - } - - renderWithEditorContext( - - ) - - const text = screen.queryByText( - 'Only the person who originally imported this Mendeley file can refresh it.' - ) - - expect(text).to.not.exist - }) - - it('shows error if provider is mendeley and current user is not the original importer of the file', function () { - const mendeleyFile: BinaryFile<'mendeley'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'mendeley', - importer_id: 'user123', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'references.bib', - type: 'file', - selected: true, - } - - renderWithEditorContext( - - ) - - const text = screen.getByText( - 'Only the person who originally imported this Mendeley file can refresh it.' - ) - - expect(text).to.exist - }) - - it('shows error if provider is zotero and current user is not the original importer of the file', function () { - const zoteroFile: BinaryFile<'zotero'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'zotero', - importer_id: 'user123', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'references.bib', - type: 'file', - selected: true, - } - - renderWithEditorContext() - - const text = screen.getByText( - 'Only the person who originally imported this Zotero file can refresh it.' - ) - - expect(text).to.exist - }) - }) -}) diff --git a/services/web/test/frontend/features/file-view/components/file-view-refresh-button.test.jsx b/services/web/test/frontend/features/file-view/components/file-view-refresh-button.test.jsx index b1ba0c2b44..14ff04eb6e 100644 --- a/services/web/test/frontend/features/file-view/components/file-view-refresh-button.test.jsx +++ b/services/web/test/frontend/features/file-view/components/file-view-refresh-button.test.jsx @@ -3,7 +3,6 @@ import { fireEvent, waitForElementToBeRemoved, } from '@testing-library/react' -import { expect } from 'chai' import fetchMock from 'fetch-mock' import FileViewRefreshButton from '@/features/file-view/components/file-view-refresh-button' import { renderWithEditorContext } from '../../../helpers/render-with-context' @@ -22,27 +21,6 @@ describe('', function () { created: new Date(2021, 1, 17, 3, 24).toISOString(), } - const thirdPartyReferenceFile = { - name: 'example.tex', - linkedFileData: { - provider: 'zotero', - importer_id: USER_ID, - }, - created: new Date(2021, 1, 17, 3, 24).toISOString(), - } - - const thirdPartyNotOriginalImporterReferenceFile = { - name: 'references.bib', - linkedFileData: { - v1_source_doc_id: 'v1-source-id', - source_project_id: 'source-project-id', - source_entity_path: '/source-entity-path.ext', - provider: 'mendeley', - importer_id: '123abc', - }, - created: new Date(2021, 1, 17, 3, 24).toISOString(), - } - beforeEach(function () { fetchMock.reset() }) @@ -65,40 +43,4 @@ describe('', function () { await screen.findByText('Refresh') }) - - it('Reindexes references after refreshing a file from a third-party provider', async function () { - fetchMock.post( - 'express:/project/:project_id/linked_file/:file_id/refresh', - { - new_file_id: '5ff7418157b4e144321df5c4', - } - ) - - renderWithEditorContext( - - ) - - fireEvent.click(screen.getByRole('button', { name: 'Refresh' })) - - await waitForElementToBeRemoved(() => - screen.getByText('Refreshing', { exact: false }) - ) - - expect(fetchMock.done()).to.be.true - - const lastCallBody = JSON.parse(fetchMock.lastCall()[1].body) - expect(lastCallBody.shouldReindexReferences).to.be.true - }) - - it('is disabled when user is not original importer', function () { - renderWithEditorContext( - - ) - - const button = screen.getByRole('button', { name: 'Refresh' }) - - expect(button.disabled).to.equal(true) - }) }) diff --git a/services/web/test/frontend/features/file-view/components/file-view-refresh-error.test.tsx b/services/web/test/frontend/features/file-view/components/file-view-refresh-error.test.tsx index 664fab56e1..72b4408357 100644 --- a/services/web/test/frontend/features/file-view/components/file-view-refresh-error.test.tsx +++ b/services/web/test/frontend/features/file-view/components/file-view-refresh-error.test.tsx @@ -1,96 +1,30 @@ import { render, screen } from '@testing-library/react' import FileViewRefreshError from '@/features/file-view/components/file-view-refresh-error' import type { BinaryFile } from '@/features/file-view/types/binary-file' -import { expect } from 'chai' describe('', function () { - describe('', function () { - it('shows correct error message for mendeley', async function () { - const mendeleyFile: BinaryFile<'mendeley'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'mendeley', - importer_id: 'user123456', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'references.bib', - type: 'file', - selected: true, - } + it('shows correct error message', function () { + const anotherProjectFile: BinaryFile<'project_file'> = { + id: '123abc', + _id: '123abc', + linkedFileData: { + provider: 'project_file', + source_project_id: 'some-id', + source_entity_path: '/path/', + }, + created: new Date(2023, 1, 17, 3, 24), + name: 'frog.jpg', + type: 'file', + selected: true, + } - render( - - ) + render( + + ) - screen.getByText(/Something’s not right!/) - screen.getByText( - /It looks like you need to re-link your Mendeley account./ - ) - - const goToSettingsLink = screen.getByRole('link', { - name: 'Go to settings', - }) - - expect(goToSettingsLink.getAttribute('href')).to.equal('/user/settings') - }) - - it('shows correct error message for zotero', async function () { - const zoteroFile: BinaryFile<'zotero'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'zotero', - importer_id: 'user123456', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'references.bib', - type: 'file', - selected: true, - } - - render( - - ) - - screen.getByText(/Something’s not right!/) - screen.getByText(/It looks like you need to re-link your Zotero account./) - - const goToSettingsLink = screen.getByRole('link', { - name: 'Go to settings', - }) - - expect(goToSettingsLink.getAttribute('href')).to.equal('/user/settings') - }) - }) - - describe('', function () { - it('shows correct error message', function () { - const anotherProjectFile: BinaryFile<'project_file'> = { - id: '123abc', - _id: '123abc', - linkedFileData: { - provider: 'project_file', - source_project_id: 'some-id', - source_entity_path: '/path/', - }, - created: new Date(2023, 1, 17, 3, 24), - name: 'frog.jpg', - type: 'file', - selected: true, - } - - render( - - ) - - screen.getByText('Access Denied: An error message') - }) + screen.getByText('Access Denied: An error message') }) })