Forráskód Böngészése

Streamline the project metadata context provider (#19384)

GitOrigin-RevId: 0b75635cb9141983827dfd0fa6a58b6182d47f22
Alf Eaton 2 éve
szülő
commit
2d2746ef24

+ 30 - 45
services/web/frontend/js/features/ide-react/context/metadata-context.tsx

@@ -11,7 +11,6 @@ import {
 import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
 import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
 import { useConnectionContext } from '@/features/ide-react/context/connection-context'
 import { useConnectionContext } from '@/features/ide-react/context/connection-context'
 import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
 import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
-import _ from 'lodash'
 import { getJSON, postJSON } from '@/infrastructure/fetch-json'
 import { getJSON, postJSON } from '@/infrastructure/fetch-json'
 import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
 import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
 import { useEditorContext } from '@/shared/context/editor-context'
 import { useEditorContext } from '@/shared/context/editor-context'
@@ -22,28 +21,31 @@ import { usePermissionsContext } from '@/features/ide-react/context/permissions-
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
 import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
 
 
-type DocumentMetadata = {
+export type Command = {
+  caption: string
+  snippet: string
+  meta: string
+  score: number
+}
+
+export type DocumentMetadata = {
   labels: string[]
   labels: string[]
-  packages: Record<string, any>
+  packages: Record<string, Command[]>
+  packageNames: string[]
 }
 }
 
 
 type DocumentsMetadata = Record<string, DocumentMetadata>
 type DocumentsMetadata = Record<string, DocumentMetadata>
 
 
-type MetadataContextValue = {
-  metadata: {
-    state: {
-      documents: DocumentsMetadata
-    }
-    getAllLabels: () => DocumentMetadata['labels']
-    getAllPackages: () => DocumentMetadata['packages']
-  }
-}
-
 type DocMetadataResponse = { docId: string; meta: DocumentMetadata }
 type DocMetadataResponse = { docId: string; meta: DocumentMetadata }
 
 
-export const MetadataContext = createContext<MetadataContextValue | undefined>(
-  undefined
-)
+export const MetadataContext = createContext<
+  | {
+      commands: Command[]
+      labels: Set<string>
+      packageNames: Set<string>
+    }
+  | undefined
+>(undefined)
 
 
 export const MetadataProvider: FC = ({ children }) => {
 export const MetadataProvider: FC = ({ children }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -65,7 +67,8 @@ export const MetadataProvider: FC = ({ children }) => {
     }: CustomEvent<IdeEvents['entity:deleted']>) => {
     }: CustomEvent<IdeEvents['entity:deleted']>) => {
       if (entity.type === 'doc') {
       if (entity.type === 'doc') {
         setDocuments(documents => {
         setDocuments(documents => {
-          return _.omit(documents, entity.entity._id)
+          delete documents[entity.entity._id]
+          return { ...documents }
         })
         })
       }
       }
     }
     }
@@ -90,23 +93,6 @@ export const MetadataProvider: FC = ({ children }) => {
     }
     }
   }, [])
   }, [])
 
 
-  const getAllLabels = useCallback(
-    () => _.flattenDeep(Object.values(documents).map(meta => meta.labels)),
-    [documents]
-  )
-
-  const getAllPackages = useCallback(() => {
-    const packageCommandMapping: Record<string, any> = {}
-    for (const meta of Object.values(documents)) {
-      for (const [packageName, commandSnippets] of Object.entries(
-        meta.packages
-      )) {
-        packageCommandMapping[packageName] = commandSnippets
-      }
-    }
-    return packageCommandMapping
-  }, [documents])
-
   const loadProjectMetaFromServer = useCallback(() => {
   const loadProjectMetaFromServer = useCallback(() => {
     getJSON(`/project/${projectId}/metadata`).then(
     getJSON(`/project/${projectId}/metadata`).then(
       (response: { projectMeta: DocumentsMetadata }) => {
       (response: { projectMeta: DocumentsMetadata }) => {
@@ -212,16 +198,15 @@ export const MetadataProvider: FC = ({ children }) => {
     }
     }
   }, [eventEmitter, loadProjectMetaFromServer, showGenericMessageModal, t])
   }, [eventEmitter, loadProjectMetaFromServer, showGenericMessageModal, t])
 
 
-  const value = useMemo<MetadataContextValue>(
-    () => ({
-      metadata: {
-        state: { documents },
-        getAllLabels,
-        getAllPackages,
-      },
-    }),
-    [documents, getAllLabels, getAllPackages]
-  )
+  const value = useMemo(() => {
+    const docs = Object.values(documents)
+
+    return {
+      commands: docs.flatMap(doc => Object.values(doc.packages).flat()),
+      labels: new Set(docs.flatMap(doc => doc.labels)),
+      packageNames: new Set(docs.flatMap(doc => doc.packageNames)),
+    }
+  }, [documents])
 
 
   return (
   return (
     <MetadataContext.Provider value={value}>
     <MetadataContext.Provider value={value}>
@@ -230,7 +215,7 @@ export const MetadataProvider: FC = ({ children }) => {
   )
   )
 }
 }
 
 
-export function useMetadataContext(): MetadataContextValue {
+export function useMetadataContext() {
   const context = useContext(MetadataContext)
   const context = useContext(MetadataContext)
 
 
   if (!context) {
   if (!context) {

+ 10 - 1
services/web/frontend/js/features/source-editor/extensions/language.ts

@@ -7,8 +7,9 @@ import {
 import { languages } from '../languages'
 import { languages } from '../languages'
 import { ViewPlugin } from '@codemirror/view'
 import { ViewPlugin } from '@codemirror/view'
 import { indentUnit, LanguageDescription } from '@codemirror/language'
 import { indentUnit, LanguageDescription } from '@codemirror/language'
-import { Metadata } from '../../../../../types/metadata'
 import { updateHasEffect } from '../utils/effects'
 import { updateHasEffect } from '../utils/effects'
+import { Folder } from '../../../../../types/folder'
+import { Command } from '@/features/ide-react/context/metadata-context'
 
 
 export const languageLoadedEffect = StateEffect.define()
 export const languageLoadedEffect = StateEffect.define()
 export const hasLanguageLoadedEffect = updateHasEffect(languageLoadedEffect)
 export const hasLanguageLoadedEffect = updateHasEffect(languageLoadedEffect)
@@ -19,6 +20,14 @@ type Options = {
   syntaxValidation: boolean
   syntaxValidation: boolean
 }
 }
 
 
+type Metadata = {
+  labels: Set<string>
+  packageNames: Set<string>
+  commands: Command[]
+  references: string[]
+  fileTreeData: Folder
+}
+
 /**
 /**
  * A state field that stores the metadata parsed from a project on the server.
  * A state field that stores the metadata parsed from a project on the server.
  */
  */

+ 5 - 5
services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts

@@ -73,7 +73,7 @@ function useCodeMirrorScope(view: EditorView) {
 
 
   const { reviewPanelOpen, miniReviewPanelVisible } = useLayoutContext()
   const { reviewPanelOpen, miniReviewPanelVisible } = useLayoutContext()
 
 
-  const { metadata } = useMetadataContext()
+  const metadata = useMetadataContext()
 
 
   const [loadingThreads] = useScopeValue<boolean>('loadingThreads')
   const [loadingThreads] = useScopeValue<boolean>('loadingThreads')
 
 
@@ -213,16 +213,16 @@ function useCodeMirrorScope(view: EditorView) {
   // set the project metadata, mostly for use in autocomplete
   // set the project metadata, mostly for use in autocomplete
   // TODO: read this data from the scope?
   // TODO: read this data from the scope?
   const metadataRef = useRef({
   const metadataRef = useRef({
-    documents: metadata.state.documents,
+    ...metadata,
     references: references.keys,
     references: references.keys,
     fileTreeData,
     fileTreeData,
   })
   })
 
 
-  // listen to project metadata (docs + packages) updates
+  // listen to project metadata (commands, labels and package names) updates
   useEffect(() => {
   useEffect(() => {
-    metadataRef.current.documents = metadata.state.documents
+    metadataRef.current = { ...metadataRef.current, ...metadata }
     view.dispatch(setMetadata(metadataRef.current))
     view.dispatch(setMetadata(metadataRef.current))
-  }, [view, metadata.state.documents])
+  }, [view, metadata])
 
 
   // listen to project reference keys updates
   // listen to project reference keys updates
   useEffect(() => {
   useEffect(() => {

+ 1 - 7
services/web/frontend/js/features/source-editor/languages/latex/completions/labels.ts

@@ -16,13 +16,7 @@ export function buildLabelCompletions(
     return
     return
   }
   }
 
 
-  const uniqueLabels = new Set(
-    Object.values(metadata.documents)
-      .map(doc => doc.labels)
-      .flat(1)
-  )
-
-  for (const label of uniqueLabels) {
+  for (const label of metadata.labels) {
     completions.labels.push({
     completions.labels.push({
       type: 'label',
       type: 'label',
       label,
       label,

+ 14 - 14
services/web/frontend/js/features/source-editor/languages/latex/completions/packages.ts

@@ -21,24 +21,24 @@ export function buildPackageCompletions(
     return
     return
   }
   }
 
 
+  // commands from packages in the project
+  for (const command of metadata.commands) {
+    completions.commands.push({
+      type: command.meta,
+      label: command.caption,
+      apply: applySnippet(command.snippet),
+      extend: extendOverUnpairedClosingBrace,
+    })
+  }
+
   const uniquePackageNames = new Set<string>(packageNames)
   const uniquePackageNames = new Set<string>(packageNames)
 
 
-  // package names and commands from packages in the project
-  for (const doc of Object.values(metadata.documents)) {
-    for (const [packageName, commands] of Object.entries(doc.packages)) {
-      uniquePackageNames.add(packageName)
-
-      for (const item of commands) {
-        completions.commands.push({
-          type: item.meta,
-          label: item.caption,
-          apply: applySnippet(item.snippet),
-          extend: extendOverUnpairedClosingBrace,
-        })
-      }
-    }
+  // package names from packages in the project
+  for (const packageName of metadata.packageNames) {
+    uniquePackageNames.add(packageName)
   }
   }
 
 
+  // exclude package names that are already in this document
   const existingPackageNames = findExistingPackageNames(context)
   const existingPackageNames = findExistingPackageNames(context)
 
 
   for (const item of uniquePackageNames) {
   for (const item of uniquePackageNames) {

+ 0 - 9
services/web/frontend/stories/decorators/scope.tsx

@@ -133,15 +133,6 @@ const initialize = () => {
         console.log('open doc', id, options)
         console.log('open doc', id, options)
       },
       },
     },
     },
-    metadataManager: {
-      metadata: {
-        state: {
-          documents: {
-            'test-file-id': { labels: ['sec:section-label'], packages: [] },
-          },
-        },
-      },
-    },
   }
   }
 
 
   // window.metaAttributesCache is reset in preview.tsx
   // window.metaAttributesCache is reset in preview.tsx

+ 26 - 185
services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx

@@ -1,6 +1,5 @@
 import { Folder } from '../../../../../types/folder'
 import { Folder } from '../../../../../types/folder'
 import { docId, mockDocContent } from '../helpers/mock-doc'
 import { docId, mockDocContent } from '../helpers/mock-doc'
-import { Metadata } from '../../../../../types/metadata'
 import { mockScope } from '../helpers/mock-scope'
 import { mockScope } from '../helpers/mock-scope'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
 import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
@@ -62,42 +61,13 @@ describe('autocomplete', { scrollBehavior: false }, function () {
       },
       },
     ]
     ]
 
 
-    const metadataManager: { metadata: { state: Metadata } } = {
-      metadata: {
-        state: {
-          documents: {
-            [docId]: {
-              labels: ['fig:frog'],
-              // TODO: add tests for packages and referencesKeys autocompletions
-              packages: {
-                foo: [
-                  {
-                    caption: 'a caption',
-                    meta: 'foo-cmd',
-                    score: 0.1,
-                    snippet: 'a caption{$1}',
-                  },
-                ],
-              },
-            },
-          },
-          references: [],
-          fileTreeData: rootFolder[0],
-        },
-      },
-    }
-
     const scope = mockScope()
     const scope = mockScope()
     scope.$root._references.keys = ['foo']
     scope.$root._references.keys = ['foo']
     scope.project.rootFolder = rootFolder
     scope.project.rootFolder = rootFolder
 
 
     cy.mount(
     cy.mount(
       <TestContainer>
       <TestContainer>
-        <EditorProviders
-          scope={scope}
-          metadataManager={metadataManager}
-          rootFolder={rootFolder as any}
-        >
+        <EditorProviders scope={scope} rootFolder={rootFolder as any}>
           <CodeMirrorEditor />
           <CodeMirrorEditor />
         </EditorProviders>
         </EditorProviders>
       </TestContainer>
       </TestContainer>
@@ -238,41 +208,12 @@ describe('autocomplete', { scrollBehavior: false }, function () {
       },
       },
     ]
     ]
 
 
-    const metadataManager: { metadata: { state: Metadata } } = {
-      metadata: {
-        state: {
-          documents: {
-            [docId]: {
-              labels: ['fig:frog'],
-              // TODO: add tests for packages and referencesKeys autocompletions
-              packages: {
-                foo: [
-                  {
-                    caption: 'a caption',
-                    meta: 'foo-cmd',
-                    score: 0.1,
-                    snippet: 'a caption{$1}',
-                  },
-                ],
-              },
-            },
-          },
-          references: [],
-          fileTreeData: rootFolder[0],
-        },
-      },
-    }
-
     const scope = mockScope()
     const scope = mockScope()
     scope.$root._references.keys = ['foo']
     scope.$root._references.keys = ['foo']
 
 
     cy.mount(
     cy.mount(
       <TestContainer>
       <TestContainer>
-        <EditorProviders
-          scope={scope}
-          metadataManager={metadataManager}
-          rootFolder={rootFolder as any}
-        >
+        <EditorProviders scope={scope} rootFolder={rootFolder as any}>
           <CodeMirrorEditor />
           <CodeMirrorEditor />
         </EditorProviders>
         </EditorProviders>
       </TestContainer>
       </TestContainer>
@@ -358,33 +299,15 @@ describe('autocomplete', { scrollBehavior: false }, function () {
       },
       },
     ]
     ]
 
 
+    const metadata = {
+      commands: [],
+      labels: new Set<string>(),
+      packageNames: new Set(['foo']),
+    }
+
     const MetadataProvider: FC = ({ children }) => {
     const MetadataProvider: FC = ({ children }) => {
       return (
       return (
-        <MetadataContext.Provider
-          value={{
-            metadata: {
-              state: {
-                documents: {
-                  [docId]: {
-                    labels: [],
-                    packages: {
-                      foo: [
-                        {
-                          caption: 'a caption',
-                          meta: 'foo-cmd',
-                          score: 0.1,
-                          snippet: 'a caption{$1}',
-                        },
-                      ],
-                    },
-                  },
-                },
-              },
-              getAllLabels: () => [],
-              getAllPackages: () => ({ foo: {} }),
-            },
-          }}
-        >
+        <MetadataContext.Provider value={metadata}>
           {children}
           {children}
         </MetadataContext.Provider>
         </MetadataContext.Provider>
       )
       )
@@ -452,36 +375,9 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     const scope = mockScope()
     const scope = mockScope()
     scope.$root._references.keys = ['ref-1', 'ref-2', 'ref-3']
     scope.$root._references.keys = ['ref-1', 'ref-2', 'ref-3']
 
 
-    const MetadataProvider: FC = ({ children }) => {
-      return (
-        <MetadataContext.Provider
-          value={{
-            metadata: {
-              state: {
-                documents: {
-                  [docId]: {
-                    labels: [],
-                    packages: {},
-                  },
-                },
-              },
-              getAllLabels: () => [],
-              getAllPackages: () => ({}),
-            },
-          }}
-        >
-          {children}
-        </MetadataContext.Provider>
-      )
-    }
-
     cy.mount(
     cy.mount(
       <TestContainer>
       <TestContainer>
-        <EditorProviders
-          scope={scope}
-          providers={MetadataProvider}
-          rootFolder={rootFolder as any}
-        >
+        <EditorProviders scope={scope} rootFolder={rootFolder as any}>
           <CodeMirrorEditor />
           <CodeMirrorEditor />
         </EditorProviders>
         </EditorProviders>
       </TestContainer>
       </TestContainer>
@@ -535,31 +431,9 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     scope.$root._references.keys = ['foo']
     scope.$root._references.keys = ['foo']
     scope.project.rootFolder = rootFolder
     scope.project.rootFolder = rootFolder
 
 
-    const MetadataProvider: FC = ({ children }) => {
-      return (
-        <MetadataContext.Provider
-          value={{
-            metadata: {
-              state: {
-                documents: {},
-              },
-              getAllLabels: () => [],
-              getAllPackages: () => ({}),
-            },
-          }}
-        >
-          {children}
-        </MetadataContext.Provider>
-      )
-    }
-
     cy.mount(
     cy.mount(
       <TestContainer>
       <TestContainer>
-        <EditorProviders
-          scope={scope}
-          rootFolder={rootFolder as any}
-          providers={MetadataProvider}
-        >
+        <EditorProviders scope={scope} rootFolder={rootFolder as any}>
           <CodeMirrorEditor />
           <CodeMirrorEditor />
         </EditorProviders>
         </EditorProviders>
       </TestContainer>
       </TestContainer>
@@ -824,33 +698,22 @@ describe('autocomplete', { scrollBehavior: false }, function () {
   it('displays unique completions for commands', function () {
   it('displays unique completions for commands', function () {
     const scope = mockScope()
     const scope = mockScope()
 
 
+    const metadata = {
+      commands: [
+        {
+          caption: '\\label{}', // label{} is also included in top-hundred-snippets
+          meta: 'amsmath-cmd',
+          score: 1,
+          snippet: '\\label{$1}',
+        },
+      ],
+      labels: new Set<string>(),
+      packageNames: new Set<string>('amsmath'),
+    }
+
     const MetadataProvider: FC = ({ children }) => {
     const MetadataProvider: FC = ({ children }) => {
       return (
       return (
-        <MetadataContext.Provider
-          value={{
-            metadata: {
-              state: {
-                documents: {
-                  [docId]: {
-                    labels: [],
-                    packages: {
-                      amsmath: [
-                        {
-                          caption: '\\label{}',
-                          meta: 'amsmath-cmd',
-                          score: 1,
-                          snippet: '\\label{$1}',
-                        },
-                      ],
-                    },
-                  },
-                },
-              },
-              getAllLabels: () => [],
-              getAllPackages: () => ({}),
-            },
-          }}
-        >
+        <MetadataContext.Provider value={metadata}>
           {children}
           {children}
         </MetadataContext.Provider>
         </MetadataContext.Provider>
       )
       )
@@ -1033,31 +896,9 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     scope.$root._references.keys = ['foo']
     scope.$root._references.keys = ['foo']
     scope.project.rootFolder = rootFolder
     scope.project.rootFolder = rootFolder
 
 
-    const MetadataProvider: FC = ({ children }) => {
-      return (
-        <MetadataContext.Provider
-          value={{
-            metadata: {
-              state: {
-                documents: {},
-              },
-              getAllLabels: () => [],
-              getAllPackages: () => ({}),
-            },
-          }}
-        >
-          {children}
-        </MetadataContext.Provider>
-      )
-    }
-
     cy.mount(
     cy.mount(
       <TestContainer>
       <TestContainer>
-        <EditorProviders
-          scope={scope}
-          providers={MetadataProvider}
-          rootFolder={rootFolder as any}
-        >
+        <EditorProviders scope={scope} rootFolder={rootFolder as any}>
           <CodeMirrorEditor />
           <CodeMirrorEditor />
         </EditorProviders>
         </EditorProviders>
       </TestContainer>
       </TestContainer>

+ 0 - 8
services/web/test/frontend/helpers/editor-providers.jsx

@@ -95,13 +95,6 @@ export function EditorProviders({
     getCurrentDocValue: () => {},
     getCurrentDocValue: () => {},
     openDoc: sinon.stub(),
     openDoc: sinon.stub(),
   },
   },
-  metadataManager = {
-    metadata: {
-      state: {
-        documents: {},
-      },
-    },
-  },
   userSettings = {},
   userSettings = {},
   providers = {},
   providers = {},
 }) {
 }) {
@@ -153,7 +146,6 @@ export function EditorProviders({
     clsiServerId,
     clsiServerId,
     editorManager,
     editorManager,
     fileTreeManager,
     fileTreeManager,
-    metadataManager,
   }
   }
 
 
   // Add details for useUserContext
   // Add details for useUserContext

+ 0 - 19
services/web/types/metadata.ts

@@ -1,19 +0,0 @@
-import { Folder } from './folder'
-
-export type Package = {
-  caption: string
-  meta: string
-  score: number
-  snippet: string
-}
-
-export type MetadataDocument = {
-  labels: string[]
-  packages: Record<string, Package[]>
-}
-
-export type Metadata = {
-  documents: Record<string, MetadataDocument>
-  references: string[]
-  fileTreeData: Folder
-}