Jelajahi Sumber

Merge pull request #4340 from overleaf/jpa-ta-hide-unavailable-new-file-options

[frontend] hide new file from project UX when feature are disabled

GitOrigin-RevId: 8407453267dded1f97d19cede5a13691610b97e7
Alexandre Bourdin 5 tahun lalu
induk
melakukan
3d963027eb

+ 4 - 0
services/web/app/src/infrastructure/ExpressLocals.js

@@ -346,6 +346,10 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) {
       hasSamlFeature: Features.hasFeature('saml'),
       hasSamlFeature: Features.hasFeature('saml'),
       samlInitPath: _.get(Settings, ['saml', 'ukamf', 'initPath']),
       samlInitPath: _.get(Settings, ['saml', 'ukamf', 'initPath']),
       hasLinkUrlFeature: Features.hasFeature('link-url'),
       hasLinkUrlFeature: Features.hasFeature('link-url'),
+      hasLinkedProjectFileFeature: Features.hasFeature('linked-project-file'),
+      hasLinkedProjectOutputFileFeature: Features.hasFeature(
+        'linked-project-output-file'
+      ),
       siteUrl: Settings.siteUrl,
       siteUrl: Settings.siteUrl,
       emailConfirmationDisabled: Settings.emailConfirmationDisabled,
       emailConfirmationDisabled: Settings.emailConfirmationDisabled,
       maxEntitiesPerProject: Settings.maxEntitiesPerProject,
       maxEntitiesPerProject: Settings.maxEntitiesPerProject,

+ 10 - 1
services/web/app/src/infrastructure/Features.js

@@ -85,8 +85,17 @@ const Features = {
         return Boolean(_.get(Settings, ['apis', 'references', 'url']))
         return Boolean(_.get(Settings, ['apis', 'references', 'url']))
       case 'saml':
       case 'saml':
         return Boolean(Settings.enableSaml)
         return Boolean(Settings.enableSaml)
+      case 'linked-project-file':
+        return Boolean(Settings.enabledLinkedFileTypes.includes('project_file'))
+      case 'linked-project-output-file':
+        return Boolean(
+          Settings.enabledLinkedFileTypes.includes('project_output_file')
+        )
       case 'link-url':
       case 'link-url':
-        return Boolean(_.get(Settings, ['apis', 'linkedUrlProxy', 'url']))
+        return Boolean(
+          _.get(Settings, ['apis', 'linkedUrlProxy', 'url']) &&
+            Settings.enabledLinkedFileTypes.includes('url')
+        )
       case 'public-registration':
       case 'public-registration':
         return publicRegistrationModuleAvailable
         return publicRegistrationModuleAvailable
       case 'support':
       case 'support':

+ 8 - 5
services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-body.js

@@ -40,11 +40,14 @@ export default function FileTreeModalCreateFileBody() {
                 label={t('upload')}
                 label={t('upload')}
               />
               />
 
 
-              <FileTreeModalCreateFileMode
-                mode="project"
-                icon="folder-open"
-                label={t('from_another_project')}
-              />
+              {(window.ExposedSettings.hasLinkedProjectFileFeature ||
+                window.ExposedSettings.hasLinkedProjectOutputFileFeature) && (
+                <FileTreeModalCreateFileMode
+                  mode="project"
+                  icon="folder-open"
+                  label={t('from_another_project')}
+                />
+              )}
 
 
               {window.ExposedSettings.hasLinkUrlFeature && (
               {window.ExposedSettings.hasLinkUrlFeature && (
                 <FileTreeModalCreateFileMode
                 <FileTreeModalCreateFileMode

+ 27 - 15
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js

@@ -16,6 +16,13 @@ import ErrorMessage from '../error-message'
 export default function FileTreeImportFromProject() {
 export default function FileTreeImportFromProject() {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
+  const {
+    hasLinkedProjectFileFeature,
+    hasLinkedProjectOutputFileFeature,
+  } = window.ExposedSettings
+  const canSwitchOutputFilesMode =
+    hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature
+
   const { name, setName, validName } = useFileTreeCreateName()
   const { name, setName, validName } = useFileTreeCreateName()
   const { setValid } = useFileTreeCreateForm()
   const { setValid } = useFileTreeCreateForm()
   const { projectId } = useFileTreeMainContext()
   const { projectId } = useFileTreeMainContext()
@@ -24,7 +31,10 @@ export default function FileTreeImportFromProject() {
   const [selectedProject, setSelectedProject] = useState()
   const [selectedProject, setSelectedProject] = useState()
   const [selectedProjectEntity, setSelectedProjectEntity] = useState()
   const [selectedProjectEntity, setSelectedProjectEntity] = useState()
   const [selectedProjectOutputFile, setSelectedProjectOutputFile] = useState()
   const [selectedProjectOutputFile, setSelectedProjectOutputFile] = useState()
-  const [isOutputFilesMode, setOutputFilesMode] = useState(false)
+  const [isOutputFilesMode, setOutputFilesMode] = useState(
+    // default to project file mode, unless the feature is not enabled
+    !hasLinkedProjectFileFeature
+  )
 
 
   // use the basename of a path as the file name
   // use the basename of a path as the file name
   const setNameFromPath = useCallback(
   const setNameFromPath = useCallback(
@@ -123,20 +133,22 @@ export default function FileTreeImportFromProject() {
         />
         />
       )}
       )}
 
 
-      <div className="toggle-file-type-button">
-        or&nbsp;
-        <Button
-          bsStyle="link"
-          type="button"
-          onClick={() => setOutputFilesMode(value => !value)}
-        >
-          <span>
-            {isOutputFilesMode
-              ? t('select_from_source_files')
-              : t('select_from_output_files')}
-          </span>
-        </Button>
-      </div>
+      {canSwitchOutputFilesMode && (
+        <div className="toggle-file-type-button">
+          or&nbsp;
+          <Button
+            bsStyle="link"
+            type="button"
+            onClick={() => setOutputFilesMode(value => !value)}
+          >
+            <span>
+              {isOutputFilesMode
+                ? t('select_from_source_files')
+                : t('select_from_output_files')}
+            </span>
+          </Button>
+        </div>
+      )}
 
 
       <FileTreeCreateNameInput
       <FileTreeCreateNameInput
         label={t('file_name_in_this_project')}
         label={t('file_name_in_this_project')}

+ 3 - 0
services/web/test/frontend/bootstrap.js

@@ -29,6 +29,9 @@ window.ExposedSettings = {
   maxEntitiesPerProject: 10,
   maxEntitiesPerProject: 10,
   maxUploadSize: 5 * 1024 * 1024,
   maxUploadSize: 5 * 1024 * 1024,
   siteUrl: 'https://www.dev-overleaf.com',
   siteUrl: 'https://www.dev-overleaf.com',
+  hasLinkUrlFeature: true,
+  hasLinkedProjectFileFeature: true,
+  hasLinkedProjectOutputFileFeature: true,
   textExtensions: [
   textExtensions: [
     'tex',
     'tex',
     'latex',
     'latex',

+ 43 - 0
services/web/test/frontend/features/file-tree/components/file-tree-create/file-tree-modal-create-file.test.js

@@ -259,6 +259,7 @@ describe('<FileTreeModalCreateFile/>', function () {
 
 
     // click on the button to toggle between source and output files
     // click on the button to toggle between source and output files
     const sourceTypeButton = screen.getByRole('button', {
     const sourceTypeButton = screen.getByRole('button', {
+      // NOTE: When changing the label, update the other tests with this label as well.
       name: 'select from output files',
       name: 'select from output files',
     })
     })
     await fireEvent.click(sourceTypeButton)
     await fireEvent.click(sourceTypeButton)
@@ -295,6 +296,48 @@ describe('<FileTreeModalCreateFile/>', function () {
     ).to.be.true
     ).to.be.true
   })
   })
 
 
+  describe('when the output files feature is not available', function () {
+    const flagBefore = window.ExposedSettings.hasLinkedProjectOutputFileFeature
+    before(function () {
+      window.ExposedSettings.hasLinkedProjectOutputFileFeature = false
+    })
+    after(function () {
+      window.ExposedSettings.hasLinkedProjectOutputFileFeature = flagBefore
+    })
+
+    it('should not show the import from output file mode', async function () {
+      fetchMock.get('path:/user/projects', {
+        projects: [
+          {
+            _id: 'test-project',
+            name: 'This Project',
+          },
+          {
+            _id: 'project-1',
+            name: 'Project One',
+          },
+          {
+            _id: 'project-2',
+            name: 'Project Two',
+          },
+        ],
+      })
+
+      render(
+        <FileTreeContext {...contextProps}>
+          <OpenWithMode mode="project" />
+        </FileTreeContext>
+      )
+
+      // should not show the toggle
+      expect(
+        screen.queryByRole('button', {
+          name: 'select from output files',
+        })
+      ).to.be.null
+    })
+  })
+
   it('import from a URL when the form is submitted', async function () {
   it('import from a URL when the form is submitted', async function () {
     fetchMock.post('express:/project/:projectId/linked_file', () => 204)
     fetchMock.post('express:/project/:projectId/linked_file', () => 204)
 
 

+ 4 - 1
services/web/test/unit/src/infrastructure/FeaturesTests.js

@@ -8,6 +8,7 @@ describe('Features', function () {
       requires: {
       requires: {
         '@overleaf/settings': (this.settings = {
         '@overleaf/settings': (this.settings = {
           moduleImportSequence: [],
           moduleImportSequence: [],
+          enabledLinkedFileTypes: [],
         }),
         }),
       },
       },
     })
     })
@@ -106,12 +107,12 @@ describe('Features', function () {
             expect(this.Features.hasFeature('affiliations')).to.be.true
             expect(this.Features.hasFeature('affiliations')).to.be.true
             expect(this.Features.hasFeature('analytics')).to.be.true
             expect(this.Features.hasFeature('analytics')).to.be.true
             expect(this.Features.hasFeature('custom-togglers')).to.be.true
             expect(this.Features.hasFeature('custom-togglers')).to.be.true
-            expect(this.Features.hasFeature('link-url')).to.be.true
             expect(this.Features.hasFeature('overleaf-integration')).to.be.true
             expect(this.Features.hasFeature('overleaf-integration')).to.be.true
             expect(this.Features.hasFeature('references')).to.be.true
             expect(this.Features.hasFeature('references')).to.be.true
             expect(this.Features.hasFeature('registration')).to.be.true
             expect(this.Features.hasFeature('registration')).to.be.true
           })
           })
           it('should return false', function () {
           it('should return false', function () {
+            expect(this.Features.hasFeature('link-url')).to.be.false
             expect(this.Features.hasFeature('git-bridge')).to.be.false
             expect(this.Features.hasFeature('git-bridge')).to.be.false
             expect(this.Features.hasFeature('github-sync')).to.be.false
             expect(this.Features.hasFeature('github-sync')).to.be.false
             expect(this.Features.hasFeature('homepage')).to.be.false
             expect(this.Features.hasFeature('homepage')).to.be.false
@@ -126,8 +127,10 @@ describe('Features', function () {
               this.settings.enableGithubSync = true
               this.settings.enableGithubSync = true
               this.settings.enableSaml = true
               this.settings.enableSaml = true
               this.settings.oauth = true
               this.settings.oauth = true
+              this.settings.enabledLinkedFileTypes = ['url', 'project_file']
             })
             })
             it('should return true or return value', function () {
             it('should return true or return value', function () {
+              expect(this.Features.hasFeature('link-url')).to.be.true
               expect(this.Features.hasFeature('affiliations')).to.be.true
               expect(this.Features.hasFeature('affiliations')).to.be.true
               expect(this.Features.hasFeature('analytics')).to.be.true
               expect(this.Features.hasFeature('analytics')).to.be.true
               expect(this.Features.hasFeature('custom-togglers')).to.be.true
               expect(this.Features.hasFeature('custom-togglers')).to.be.true