diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index a98a8cb883..9114ac5912 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -499,7 +499,6 @@ "hotkeys": "", "how_it_works": "", "i_want_to_stay": "", - "if_error_persists_try_relinking_provider": "", "if_you_need_to_customize_your_table_further_you_can": "", "ignore_validation_errors": "", "ill_take_it": "", @@ -752,6 +751,7 @@ "only_group_admin_or_managers_can_delete_your_account_3": "", "only_group_admin_or_managers_can_delete_your_account_4": "", "only_group_admin_or_managers_can_delete_your_account_5": "", + "only_importer_can_refresh": "", "open_file": "", "open_link": "", "open_project": "", @@ -1035,6 +1035,7 @@ "showing_x_results_of_total": "", "signature_algorithm": "", "single_sign_on_sso": "", + "something_not_right": "", "something_went_wrong_loading_pdf_viewer": "", "something_went_wrong_processing_the_request": "", "something_went_wrong_rendering_pdf": "", @@ -1215,6 +1216,7 @@ "try_it_for_free": "", "try_premium_for_free": "", "try_recompile_project_or_troubleshoot": "", + "try_relinking_provider": "", "try_to_compile_despite_errors": "", "turn_off_link_sharing": "", "turn_on_link_sharing": "", 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 59cf2d0904..0f6a9d41a3 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 @@ -1,30 +1,25 @@ -import { useState, useCallback, type ElementType } from 'react' +import { useState, type ElementType } from 'react' import PropTypes from 'prop-types' import { Trans, useTranslation } from 'react-i18next' import Icon from '../../../shared/components/icon' import { formatTime, relativeDate } from '../../utils/format-date' -import { postJSON } from '../../../infrastructure/fetch-json' import { useEditorContext } from '../../../shared/context/editor-context' import { useProjectContext } from '../../../shared/context/project-context' +import { Nullable } from '../../../../../types/utils' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' -import useAbortController from '../../../shared/hooks/use-abort-controller' 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 } path: string }[] -const tprLinkedFileRefreshError = importOverleafModules( - 'tprLinkedFileRefreshError' -) as { - import: { LinkedFileRefreshError: ElementType } - path: string -}[] - const MAX_URL_LENGTH = 60 const FRONT_OF_URL_LENGTH = 35 const FILLER = '...' @@ -55,10 +50,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) { }) const { t } = useTranslation() - const [refreshing, setRefreshing] = useState(false) - const [refreshError, setRefreshError] = useState(null) - - const { signal } = useAbortController() + const [refreshError, setRefreshError] = useState>(null) let fileInfo if (file.linkedFileData) { @@ -83,29 +75,6 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) { } } - const refreshFile = useCallback(() => { - setRefreshing(true) - // Replacement of the file handled by the file tree - window.expectingLinkedFileRefreshedSocketFor = file.name - const body = { - shouldReindexReferences: - file.linkedFileData?.provider === 'mendeley' || - file.linkedFileData?.provider === 'zotero' || - /\.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]) - return (
{file.linkedFileData && fileInfo} @@ -114,14 +83,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) { ))} {file.linkedFileData && permissionsLevel !== 'readOnly' && ( - + )}   {t('download')} + {file.linkedFileData && } {refreshError && ( -
-
-
- {t('access_denied')}: {refreshError} - {tprLinkedFileRefreshError.map( - ({ import: { LinkedFileRefreshError }, path }) => ( - - ) - )} -
-
+ )}
) 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 new file mode 100644 index 0000000000..a18b1c3a5d --- /dev/null +++ b/services/web/frontend/js/features/file-view/components/file-view-not-original-importer.tsx @@ -0,0 +1,38 @@ +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 new file mode 100644 index 0000000000..b3e4eee1e7 --- /dev/null +++ b/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx @@ -0,0 +1,76 @@ +import { + type Dispatch, + type SetStateAction, + 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 { Nullable } from '../../../../../types/utils' + +type FileViewRefreshButtonProps = { + setRefreshError: Dispatch>> + file: BinaryFile +} + +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 isMendeleyOrZotero = + hasProvider(file, 'mendeley') || hasProvider(file, 'zotero') + + let isImporter + + if (isMendeleyOrZotero) { + isImporter = file.linkedFileData.importer_id === userId + } + + const buttonClickable = isMendeleyOrZotero ? isImporter : true + + 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]) + + return ( + + ) +} diff --git a/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx b/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx new file mode 100644 index 0000000000..f6e3de516f --- /dev/null +++ b/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx @@ -0,0 +1,87 @@ +import type { ElementType } from 'react' +import { useTranslation } from 'react-i18next' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' +import { BinaryFile, hasProvider } from '../types/binary-file' + +const tprLinkedFileRefreshError = importOverleafModules( + 'tprLinkedFileRefreshError' +) as { + import: { LinkedFileRefreshError: ElementType } + path: string +}[] + +type FileViewRefreshErrorProps = { + file: BinaryFile + refreshError: string +} + +export default function FileViewRefreshError({ + file, + refreshError, +}: FileViewRefreshErrorProps) { + const isMendeleyOrZotero = + hasProvider(file, 'mendeley') || hasProvider(file, 'zotero') + + return ( +
+
+ + {isMendeleyOrZotero ? ( + + ) : ( + + )} +
+ ) +} + +type FileViewMendeleyOrZoteroRefreshErrorProps = { + file: BinaryFile +} + +function FileViewMendeleyOrZoteroRefreshError({ + file, +}: FileViewMendeleyOrZoteroRefreshErrorProps) { + const { t } = useTranslation() + return ( +
+
+ {t('something_not_right')}!  + {tprLinkedFileRefreshError.map( + ({ import: { LinkedFileRefreshError }, path }) => ( + + ) + )} +
+
+ +
+
+ ) +} + +type FileViewDefaultRefreshErrorProps = { + refreshError: string +} + +function FileViewDefaultRefreshError({ + refreshError, +}: FileViewDefaultRefreshErrorProps) { + const { t } = useTranslation() + return ( +
+ {t('access_denied')}: {refreshError} +
+ ) +} diff --git a/services/web/locales/da.json b/services/web/locales/da.json index d7eb0fd9dd..c1ef43b904 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -705,7 +705,6 @@ "how_to_insert_images": "Hvordan indsætter jeg figurer", "hundreds_templates_info": "Skab smukke dokumenter ved at starte fra vores galleri af LaTeX skabeloner for journaler, konferencer, afhandlinger, rapporter, CV’er og meget mere.", "i_want_to_stay": "Jeg ønsker at blive", - "if_error_persists_try_relinking_provider": "Hvis denne fejl fortsætter kan du prøve at re-etablere fobindelsen til din __provider__ konto her", "if_have_existing_can_link": "Hvis du har en eksisterende __appName__-konto under en anden e-mailaddresse, kan du forbinde den til din __institutionName__-konto ved at klikke __clickText__", "if_owner_can_link": "Hvis du ejer __appName__-kontoen under __email__, vil du få mulighed for at forbinde den til din institutionelle konto hos __institutionName__.", "ignore_and_continue_institution_linking": "Du kan også springe det over, og fortsætte til __appName__ med kontoen for __email__.", diff --git a/services/web/locales/de.json b/services/web/locales/de.json index cd02ec82f3..9578084505 100644 --- a/services/web/locales/de.json +++ b/services/web/locales/de.json @@ -711,7 +711,6 @@ "how_to_insert_images": "So fügst du Bilder ein", "hundreds_templates_info": "Erstelle schöne Dokumente ausgehend von unserer Galerie mit LaTeX-Vorlagen für Zeitschriften, Konferenzen, Abschlussarbeiten, Berichte, Lebensläufe und vieles mehr.", "i_want_to_stay": "Ich möchte bleiben", - "if_error_persists_try_relinking_provider": "Wenn dieser Fehler weiterhin besteht, versuche, dein __provider__-Konto hier erneut zu verknüpfen", "if_have_existing_can_link": "Wenn du ein vorhandenes __appName__-Konto mit einer anderen E-Mail-Adresse hast, kannst du es mit deinem __institutionName__-Konto verknüpfen, indem du auf „__clickText__“ klickst.", "if_owner_can_link": "Wenn du das __appName__ Konto mit __email__ besitzt, kannst du es mit deinem institutionellen Konto __institutionName__ verknüpfen.", "ignore_and_continue_institution_linking": "Du kannst dies auch ignorieren und weiter zu __appName__ mit deinem __email__-Konto gehen.", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index b11f9b0907..0bdb90d0a0 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -786,7 +786,6 @@ "how_to_insert_images": "How to insert images", "hundreds_templates_info": "Produce beautiful documents starting from our gallery of LaTeX templates for journals, conferences, theses, reports, CVs and much more.", "i_want_to_stay": "I want to stay", - "if_error_persists_try_relinking_provider": "If this error persists, try re-linking your __provider__ account here", "if_have_existing_can_link": "If you have an existing __appName__ account on another email, you can link it to your __institutionName__ account by clicking __clickText__.", "if_owner_can_link": "If you own the __appName__ account with __email__, you will be allowed to link it to your __institutionName__ institutional account.", "if_you_need_to_customize_your_table_further_you_can": "If you need to customize your table further, you can. Using LaTeX code, you can change anything from table styles and border styles to colors and column widths. <0>Read our guide to using tables in LaTeX to help you get started.", @@ -1194,6 +1193,7 @@ "only_group_admin_or_managers_can_delete_your_account_3": "Your group admin and group managers will be able to reassign ownership of your projects to another group member.", "only_group_admin_or_managers_can_delete_your_account_4": "Once you have become a managed user, you cannot change back. <0>Learn more about managed Overleaf accounts.", "only_group_admin_or_managers_can_delete_your_account_5": "For more information, see the \"Managed Accounts\" section in our terms of use, which you agree to by clicking Accept invitation", + "only_importer_can_refresh": "Only the person who originally imported this __provider__ file can refresh it.", "open_a_file_on_the_left": "Open a file on the left", "open_as_template": "Open as Template", "open_file": "Edit file", @@ -1611,6 +1611,7 @@ "skip_to_content": "Skip to content", "sl_gives_you_free_stuff_see_progress_below": "When someone starts using __appName__ after your recommendation we’ll give you some free stuff to say thanks! Check your progress below.", "sl_included_history_of_changes_blurb": "__appName__ includes a history of all of your changes so you can see exactly who changed what, and when. This makes it extremely easy to keep up to date with any progress made by your collaborators and allows you to review recent work.", + "something_not_right": "Something’s not right", "something_went_wrong_canceling_your_subscription": "Something went wrong canceling your subscription. Please contact support.", "something_went_wrong_loading_pdf_viewer": "Something went wrong loading the PDF viewer. This might be caused by issues like <0>temporary network problems or an <0>outdated web browser. Please follow the <1>troubleshooting steps for access, loading and display problems. If the issue persists, please <2>let us know.", "something_went_wrong_processing_the_request": "Something went wrong processing the request", @@ -1858,6 +1859,7 @@ "try_now": "Try Now", "try_premium_for_free": "Try Premium for free", "try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesn’t help, follow our <0>troubleshooting guide.", + "try_relinking_provider": "It looks like you need to re-link your __provider__ account.", "try_to_compile_despite_errors": "Try to compile despite errors", "turn_off_link_sharing": "Turn off link sharing", "turn_on_link_sharing": "Turn on link sharing", diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index e5563cd772..36b122143d 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -447,7 +447,6 @@ "hotkeys": "快捷键", "hundreds_templates_info": "从我们的 LaTeX 模板库开始,为期刊、会议、论文、报告、简历等制作漂亮的文档。", "i_want_to_stay": "我要留下", - "if_error_persists_try_relinking_provider": "如果此错误仍然存在,请尝试在此处重新链接您的__provider__帐户", "if_have_existing_can_link": "如果您在另一封电子邮件中有一个现有的 __appName__ 帐户,您可以通过单击 __clickText__ 将其链接到您的 __institutionName__ 账户。", "if_owner_can_link": "如果您在__appName__拥有账户__email__,您可以将其链接到您的 __institutionName__ 机构帐户。", "ignore_and_continue_institution_linking": "您也可以忽略此项,然后继续在 __appName__ 上使用您的 __email__ 帐户。", diff --git a/services/web/test/frontend/features/file-view/components/file-view-header.test.jsx b/services/web/test/frontend/features/file-view/components/file-view-header.test.jsx index 5751a4edf8..e3e394fb72 100644 --- a/services/web/test/frontend/features/file-view/components/file-view-header.test.jsx +++ b/services/web/test/frontend/features/file-view/components/file-view-header.test.jsx @@ -1,13 +1,9 @@ -import { - screen, - fireEvent, - waitForElementToBeRemoved, -} from '@testing-library/react' -import { expect } from 'chai' +import { screen } from '@testing-library/react' import fetchMock from 'fetch-mock' import { renderWithEditorContext } from '../../../helpers/render-with-context' import FileViewHeader from '../../../../../frontend/js/features/file-view/components/file-view-header' +import { USER_ID } from '../../../helpers/editor-providers' describe('', function () { const urlFile = { @@ -26,6 +22,7 @@ describe('', function () { source_project_id: 'source-project-id', source_entity_path: '/source-entity-path.ext', provider: 'project_file', + importer_id: USER_ID, }, created: new Date(2021, 1, 17, 3, 24).toISOString(), } @@ -40,14 +37,6 @@ describe('', function () { created: new Date(2021, 1, 17, 3, 24).toISOString(), } - const thirdPartyReferenceFile = { - name: 'example.tex', - linkedFileData: { - provider: 'zotero', - }, - created: new Date(2021, 1, 17, 3, 24).toISOString(), - } - beforeEach(function () { fetchMock.reset() }) @@ -85,48 +74,6 @@ describe('', function () { }) }) - describe('The refresh button', async function () { - it('Changes text when the file is refreshing', 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 }) - ) - 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 - }) - }) - describe('The download button', function () { it('exists', function () { renderWithEditorContext() 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 new file mode 100644 index 0000000000..c5da1f3ead --- /dev/null +++ b/services/web/test/frontend/features/file-view/components/file-view-not-original-importer.test.tsx @@ -0,0 +1,108 @@ +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 new file mode 100644 index 0000000000..b1ba0c2b44 --- /dev/null +++ b/services/web/test/frontend/features/file-view/components/file-view-refresh-button.test.jsx @@ -0,0 +1,104 @@ +import { + screen, + 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' +import { USER_ID } from '../../../helpers/editor-providers' + +describe('', function () { + const projectFile = { + name: 'example.tex', + linkedFileData: { + v1_source_doc_id: 'v1-source-id', + source_project_id: 'source-project-id', + source_entity_path: '/source-entity-path.ext', + provider: 'project_file', + importer_id: USER_ID, + }, + 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() + }) + + it('Changes text when the file is refreshing', 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 }) + ) + + 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 new file mode 100644 index 0000000000..664fab56e1 --- /dev/null +++ b/services/web/test/frontend/features/file-view/components/file-view-refresh-error.test.tsx @@ -0,0 +1,96 @@ +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, + } + + 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') + }) + }) +})