add an option to remove chat from server pro (#20445)

* add option to remove chat from backend

* make chat default to enabled

* Check chat is enabled in chat context

---------

Co-authored-by: mserranom <mserranom@gmail.com>
GitOrigin-RevId: 7dda09df4bb74007eb4b1272d4918155b5cddaf6
This commit is contained in:
Brian Gough 2024-10-09 10:17:12 +01:00 committed by Copybot
parent ce130a8bc5
commit 4a32f49b3d
12 changed files with 92 additions and 39 deletions

View file

@ -719,6 +719,7 @@ const _ProjectController = {
isTokenMember, isTokenMember,
isInvitedMember isInvitedMember
), ),
chatEnabled: Features.hasFeature('chat'),
roMirrorOnClientNoLocalStorage: roMirrorOnClientNoLocalStorage:
Settings.adminOnlyLogin || project.name.startsWith('Debug: '), Settings.adminOnlyLogin || project.name.startsWith('Debug: '),
languages: Settings.languages, languages: Settings.languages,

View file

@ -58,6 +58,8 @@ const Features = {
) )
case 'registration': case 'registration':
return Boolean(Settings.overleaf) return Boolean(Settings.overleaf)
case 'chat':
return Boolean(Settings.disableChat) === false
case 'github-sync': case 'github-sync':
return Boolean(Settings.enableGithubSync) return Boolean(Settings.enableGithubSync)
case 'git-bridge': case 'git-bridge':

View file

@ -1139,19 +1139,21 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
SpellingController.unlearn SpellingController.unlearn
) )
webRouter.get( if (Features.hasFeature('chat')) {
'/project/:project_id/messages', webRouter.get(
AuthorizationMiddleware.blockRestrictedUserFromProject, '/project/:project_id/messages',
AuthorizationMiddleware.ensureUserCanReadProject, AuthorizationMiddleware.blockRestrictedUserFromProject,
ChatController.getMessages AuthorizationMiddleware.ensureUserCanReadProject,
) ChatController.getMessages
webRouter.post( )
'/project/:project_id/messages', webRouter.post(
AuthorizationMiddleware.blockRestrictedUserFromProject, '/project/:project_id/messages',
AuthorizationMiddleware.ensureUserCanReadProject, AuthorizationMiddleware.blockRestrictedUserFromProject,
RateLimiterMiddleware.rateLimit(rateLimiters.sendChatMessage), AuthorizationMiddleware.ensureUserCanReadProject,
ChatController.sendMessage RateLimiterMiddleware.rateLimit(rateLimiters.sendChatMessage),
) ChatController.sendMessage
)
}
webRouter.post( webRouter.post(
'/project/:Project_id/references/indexAll', '/project/:Project_id/references/indexAll',

View file

@ -9,6 +9,7 @@ meta(name="ol-isTokenMember" data-type="boolean" content=isTokenMember)
meta(name="ol-isRestrictedTokenMember" data-type="boolean" content=isRestrictedTokenMember) meta(name="ol-isRestrictedTokenMember" data-type="boolean" content=isRestrictedTokenMember)
meta(name="ol-maxDocLength" data-type="json" content=maxDocLength) meta(name="ol-maxDocLength" data-type="json" content=maxDocLength)
meta(name="ol-wikiEnabled" data-type="boolean" content=settings.proxyLearn) meta(name="ol-wikiEnabled" data-type="boolean" content=settings.proxyLearn)
meta(name="ol-chatEnabled" data-type="boolean" content=chatEnabled)
meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl) meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl)
meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled) meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled)
meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain) meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain)

View file

@ -407,6 +407,7 @@ module.exports = {
}, },
], ],
disableChat: process.env.OVERLEAF_DISABLE_CHAT === 'true',
enableSubscriptions: false, enableSubscriptions: false,
restrictedCountries: [], restrictedCountries: [],
enableOnboardingEmails: process.env.ENABLE_ONBOARDING_EMAILS === 'true', enableOnboardingEmails: process.env.ENABLE_ONBOARDING_EMAILS === 'true',

View file

@ -17,6 +17,8 @@ import { appendMessage, prependMessages } from '../utils/message-list-appender'
import useBrowserWindow from '../../../shared/hooks/use-browser-window' import useBrowserWindow from '../../../shared/hooks/use-browser-window'
import { useLayoutContext } from '../../../shared/context/layout-context' import { useLayoutContext } from '../../../shared/context/layout-context'
import { useIdeContext } from '@/shared/context/ide-context' import { useIdeContext } from '@/shared/context/ide-context'
import getMeta from '@/utils/meta'
import { debugConsole } from '@/utils/debugging'
const PAGE_SIZE = 50 const PAGE_SIZE = 50
@ -187,6 +189,8 @@ export const ChatContext = createContext<
>(undefined) >(undefined)
export const ChatProvider: FC = ({ children }) => { export const ChatProvider: FC = ({ children }) => {
const chatEnabled = getMeta('ol-chatEnabled')
const clientId = useRef<string>() const clientId = useRef<string>()
if (clientId.current === undefined) { if (clientId.current === undefined) {
clientId.current = chatClientIdGenerator.generate() clientId.current = chatClientIdGenerator.generate()
@ -235,6 +239,10 @@ export const ChatProvider: FC = ({ children }) => {
} }
function loadInitialMessages() { function loadInitialMessages() {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't load initial messages`)
return
}
if (state.initialMessagesLoaded) return if (state.initialMessagesLoaded) return
dispatch({ type: 'INITIAL_FETCH_MESSAGES' }) dispatch({ type: 'INITIAL_FETCH_MESSAGES' })
@ -242,11 +250,19 @@ export const ChatProvider: FC = ({ children }) => {
} }
function loadMoreMessages() { function loadMoreMessages() {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't load messages`)
return
}
dispatch({ type: 'FETCH_MESSAGES' }) dispatch({ type: 'FETCH_MESSAGES' })
fetchMessages() fetchMessages()
} }
function reset() { function reset() {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't reset chat`)
return
}
dispatch({ type: 'CLEAR' }) dispatch({ type: 'CLEAR' })
fetchMessages() fetchMessages()
} }
@ -256,10 +272,20 @@ export const ChatProvider: FC = ({ children }) => {
loadMoreMessages, loadMoreMessages,
reset, reset,
} }
}, [projectId, state.atEnd, state.initialMessagesLoaded, state.lastTimestamp]) }, [
chatEnabled,
projectId,
state.atEnd,
state.initialMessagesLoaded,
state.lastTimestamp,
])
const sendMessage = useCallback( const sendMessage = useCallback(
content => { content => {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't send message`)
return
}
if (!content) return if (!content) return
dispatch({ dispatch({
@ -278,17 +304,21 @@ export const ChatProvider: FC = ({ children }) => {
}) })
}) })
}, },
[projectId, user] [chatEnabled, projectId, user]
) )
const markMessagesAsRead = useCallback(() => { const markMessagesAsRead = useCallback(() => {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't mark messages as read`)
return
}
dispatch({ type: 'MARK_MESSAGES_AS_READ' }) dispatch({ type: 'MARK_MESSAGES_AS_READ' })
}, []) }, [chatEnabled])
// Handling receiving messages over the socket // Handling receiving messages over the socket
const { socket } = useIdeContext() const { socket } = useIdeContext()
useEffect(() => { useEffect(() => {
if (!socket) return if (!chatEnabled || !socket) return
function receivedMessage(message: any) { function receivedMessage(message: any) {
// If the message is from the current client id, then we are receiving the sent message back from the socket. // If the message is from the current client id, then we are receiving the sent message back from the socket.
@ -304,7 +334,7 @@ export const ChatProvider: FC = ({ children }) => {
socket.removeListener('new-chat-message', receivedMessage) socket.removeListener('new-chat-message', receivedMessage)
} }
}, [socket]) }, [chatEnabled, socket])
// Handle unread messages // Handle unread messages
useEffect(() => { useEffect(() => {

View file

@ -51,6 +51,8 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
openShareModal, openShareModal,
trackChangesVisible, trackChangesVisible,
}) { }) {
const chatEnabled = getMeta('ol-chatEnabled')
const { t } = useTranslation() const { t } = useTranslation()
const shouldDisplayPublishButton = hasPublishPermissions && PublishButton const shouldDisplayPublishButton = hasPublishPermissions && PublishButton
@ -123,7 +125,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
<LayoutDropdownButton /> <LayoutDropdownButton />
{chatVisible && ( {chatEnabled && chatVisible && (
<ChatToggleButton <ChatToggleButton
chatIsOpen={chatIsOpen} chatIsOpen={chatIsOpen}
onClick={toggleChatOpen} onClick={toggleChatOpen}

View file

@ -13,6 +13,7 @@ import { useSidebarPane } from '@/features/ide-react/hooks/use-sidebar-pane'
import { useChatPane } from '@/features/ide-react/hooks/use-chat-pane' import { useChatPane } from '@/features/ide-react/hooks/use-chat-pane'
import { EditorAndPdf } from '@/features/ide-react/components/editor-and-pdf' import { EditorAndPdf } from '@/features/ide-react/components/editor-and-pdf'
import HistoryContainer from '@/features/ide-react/components/history-container' import HistoryContainer from '@/features/ide-react/components/history-container'
import getMeta from '@/utils/meta'
export const MainLayout: FC = () => { export const MainLayout: FC = () => {
const { view } = useLayoutContext() const { view } = useLayoutContext()
@ -38,6 +39,8 @@ export const MainLayout: FC = () => {
handlePaneExpand: handleChatExpand, handlePaneExpand: handleChatExpand,
} = useChatPane() } = useChatPane()
const chatEnabled = getMeta('ol-chatEnabled')
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -90,27 +93,31 @@ export const MainLayout: FC = () => {
<EditorAndPdf /> <EditorAndPdf />
</Panel> </Panel>
<HorizontalResizeHandle {chatEnabled && (
onDoubleClick={toggleChat} <>
resizable={chatIsOpen} <HorizontalResizeHandle
onDragging={setChatResizing} onDoubleClick={toggleChat}
hitAreaMargins={{ coarse: 0, fine: 0 }} resizable={chatIsOpen}
/> onDragging={setChatResizing}
hitAreaMargins={{ coarse: 0, fine: 0 }}
/>
{/* chat */} {/* chat */}
<Panel <Panel
ref={chatPanelRef} ref={chatPanelRef}
id="panel-chat" id="panel-chat"
order={2} order={2}
defaultSize={20} defaultSize={20}
minSize={5} minSize={5}
maxSize={30} maxSize={30}
collapsible collapsible
onCollapse={handleChatCollapse} onCollapse={handleChatCollapse}
onExpand={handleChatExpand} onExpand={handleChatExpand}
> >
<ChatPane /> <ChatPane />
</Panel> </Panel>
</>
)}
</PanelGroup> </PanelGroup>
</Panel> </Panel>
</PanelGroup> </PanelGroup>

View file

@ -69,6 +69,7 @@ export interface Meta {
'ol-cannot-link-other-third-party-sso': boolean 'ol-cannot-link-other-third-party-sso': boolean
'ol-cannot-reactivate-subscription': boolean 'ol-cannot-reactivate-subscription': boolean
'ol-cannot-use-ai': boolean 'ol-cannot-use-ai': boolean
'ol-chatEnabled': boolean
'ol-countryCode': PricingFormState['country'] 'ol-countryCode': PricingFormState['country']
'ol-couponCode': PricingFormState['coupon'] 'ol-couponCode': PricingFormState['coupon']
'ol-createdAt': Date 'ol-createdAt': Date

View file

@ -22,6 +22,7 @@ describe('<ChatPane />', function () {
beforeEach(function () { beforeEach(function () {
window.metaAttributesCache.set('ol-user', user) window.metaAttributesCache.set('ol-user', user)
window.metaAttributesCache.set('ol-chatEnabled', true)
}) })
afterEach(function () { afterEach(function () {

View file

@ -29,6 +29,7 @@ describe('ChatContext', function () {
stubMathJax() stubMathJax()
window.metaAttributesCache.set('ol-user', user) window.metaAttributesCache.set('ol-user', user)
window.metaAttributesCache.set('ol-chatEnabled', true)
this.stub = sinon.stub(chatClientIdGenerator, 'generate').returns(uuidValue) this.stub = sinon.stub(chatClientIdGenerator, 'generate').returns(uuidValue)
}) })

View file

@ -26,6 +26,10 @@ describe('<ToolbarHeader />', function () {
detach: () => {}, detach: () => {},
} }
beforeEach(function () {
window.metaAttributesCache.set('ol-chatEnabled', true)
})
describe('cobranding logo', function () { describe('cobranding logo', function () {
it('is not displayed by default', function () { it('is not displayed by default', function () {
renderWithEditorContext(<ToolbarHeader {...defaultProps} />) renderWithEditorContext(<ToolbarHeader {...defaultProps} />)