error-logs.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { useTranslation } from 'react-i18next'
  2. import { ElementType, memo, useCallback, useMemo, useState } from 'react'
  3. import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
  4. import StopOnFirstErrorPrompt from '@/features/pdf-preview/components/stop-on-first-error-prompt'
  5. import PdfPreviewError from '@/features/pdf-preview/components/pdf-preview-error'
  6. import PdfValidationIssue from '@/features/pdf-preview/components/pdf-validation-issue'
  7. import PdfLogsEntries from '@/features/pdf-preview/components/pdf-logs-entries'
  8. import PdfPreviewErrorBoundaryFallback from '@/features/pdf-preview/components/pdf-preview-error-boundary-fallback'
  9. import withErrorBoundary from '@/infrastructure/error-boundary'
  10. import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
  11. import { Nav, NavLink, TabContainer, TabContent } from 'react-bootstrap'
  12. import { LogEntry as LogEntryData } from '@/features/pdf-preview/util/types'
  13. import LogEntry from './log-entry'
  14. import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
  15. import TimeoutUpgradePromptNew from '@/features/pdf-preview/components/timeout-upgrade-prompt-new'
  16. import getMeta from '@/utils/meta'
  17. import PdfClearCacheButton from '@/features/pdf-preview/components/pdf-clear-cache-button'
  18. import PdfDownloadFilesButton from '@/features/pdf-preview/components/pdf-download-files-button'
  19. import RollingBuildSelectedReminder from './rolling-build-selected-reminder'
  20. const logsComponents: Array<{
  21. import: { default: ElementType }
  22. path: string
  23. }> = importOverleafModules('errorLogsComponents')
  24. type ErrorLogTab = {
  25. key: string
  26. label: string
  27. entries: LogEntryData[] | undefined
  28. }
  29. function ErrorLogs({
  30. includeActionButtons,
  31. }: {
  32. includeActionButtons?: boolean
  33. }) {
  34. const { error, logEntries, rawLog, validationIssues, stoppedOnFirstError } =
  35. useCompileContext()
  36. const { compileTimeout } = getMeta('ol-compileSettings')
  37. const { t } = useTranslation()
  38. const tabs = useMemo(() => {
  39. return [
  40. {
  41. key: 'all',
  42. label: t('all_logs'),
  43. entries: logEntries?.all,
  44. },
  45. { key: 'errors', label: t('errors'), entries: logEntries?.errors },
  46. { key: 'warnings', label: t('warnings'), entries: logEntries?.warnings },
  47. { key: 'info', label: t('info'), entries: logEntries?.typesetting },
  48. ]
  49. }, [logEntries, t])
  50. const { loadingError } = usePdfPreviewContext()
  51. const [activeTab, setActiveTab] = useState<string | null>('all')
  52. const changeTab = useCallback(
  53. (key: string | null) => {
  54. if (tabs.some(tab => tab.key === key)) {
  55. setActiveTab(key)
  56. }
  57. },
  58. [tabs]
  59. )
  60. const entries = useMemo(() => {
  61. return tabs.find(tab => tab.key === activeTab)?.entries || []
  62. }, [activeTab, tabs])
  63. const includeErrors = activeTab === 'all' || activeTab === 'errors'
  64. const includeWarnings = activeTab === 'all' || activeTab === 'warnings'
  65. return (
  66. <TabContainer onSelect={changeTab} defaultActiveKey={activeTab ?? 'all'}>
  67. <Nav defaultActiveKey="all" className="error-logs-tabs">
  68. {tabs.map(tab => (
  69. <TabHeader key={tab.key} tab={tab} active={activeTab === tab.key} />
  70. ))}
  71. </Nav>
  72. {logsComponents.map(({ import: { default: Component }, path }) => (
  73. <Component key={path} />
  74. ))}
  75. <TabContent className="error-logs new-error-logs">
  76. <div className="logs-pane-content">
  77. <RollingBuildSelectedReminder />
  78. {stoppedOnFirstError && includeErrors && <StopOnFirstErrorPrompt />}
  79. {loadingError && (
  80. <PdfPreviewError
  81. error="pdf-viewer-loading-error"
  82. includeErrors={includeErrors}
  83. includeWarnings={includeWarnings}
  84. />
  85. )}
  86. {compileTimeout < 60 && error === 'timedout' ? (
  87. <TimeoutUpgradePromptNew />
  88. ) : (
  89. <>{error && <PdfPreviewError error={error} />}</>
  90. )}
  91. {includeErrors &&
  92. validationIssues &&
  93. Object.entries(validationIssues).map(([name, issue]) => (
  94. <PdfValidationIssue key={name} name={name} issue={issue} />
  95. ))}
  96. {entries && (
  97. <PdfLogsEntries
  98. entries={entries}
  99. hasErrors={
  100. includeErrors &&
  101. logEntries?.errors &&
  102. logEntries?.errors.length > 0
  103. }
  104. />
  105. )}
  106. {rawLog && activeTab === 'all' && (
  107. <LogEntry
  108. headerTitle={t('raw_logs')}
  109. rawContent={rawLog}
  110. entryAriaLabel={t('raw_logs_description')}
  111. level="raw"
  112. alwaysExpandRawContent
  113. showSourceLocationLink={false}
  114. />
  115. )}
  116. {includeActionButtons && (
  117. <div className="logs-pane-actions">
  118. <PdfClearCacheButton />
  119. <PdfDownloadFilesButton />
  120. </div>
  121. )}
  122. </div>
  123. </TabContent>
  124. </TabContainer>
  125. )
  126. }
  127. function formatErrorNumber(num: number | undefined) {
  128. if (num === undefined) {
  129. return undefined
  130. }
  131. if (num > 99) {
  132. return '99+'
  133. }
  134. return Math.floor(num).toString()
  135. }
  136. const TabHeader = ({ tab, active }: { tab: ErrorLogTab; active: boolean }) => {
  137. return (
  138. <NavLink
  139. eventKey={tab.key}
  140. className="error-logs-tab-header"
  141. active={active}
  142. >
  143. {tab.label}
  144. <div className="error-logs-tab-count">
  145. {/* TODO: it would be nice if this number included custom errors */}
  146. {formatErrorNumber(tab.entries?.length)}
  147. </div>
  148. </NavLink>
  149. )
  150. }
  151. export default withErrorBoundary(memo(ErrorLogs), () => (
  152. <PdfPreviewErrorBoundaryFallback type="logs" />
  153. ))