diff --git a/assist/utils/httpHandlers.js b/assist/utils/httpHandlers.js index 67b5b06f7..c6514e2dc 100644 --- a/assist/utils/httpHandlers.js +++ b/assist/utils/httpHandlers.js @@ -27,7 +27,9 @@ const respond = function (req, res, data) { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(result)); } else { - res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify(result)); + res.cork(() => { + res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify(result)); + }); } const duration = performance.now() - req.startTs; IncreaseTotalRequests(); diff --git a/backend/pkg/integrations/clients/sentry.go b/backend/pkg/integrations/clients/sentry.go index b70d88ad8..7dcc284eb 100644 --- a/backend/pkg/integrations/clients/sentry.go +++ b/backend/pkg/integrations/clients/sentry.go @@ -31,11 +31,40 @@ type SentryEvent struct { } func (s *sentryClient) FetchSessionData(credentials interface{}, sessionID uint64) (interface{}, error) { + cfg, err := parseSentryConfig(credentials) + if err != nil { + return nil, err + } + // Fetch sentry events + requestUrl := prepareURLWithParams(cfg, sessionID, true) + list, err := makeRequest(cfg, requestUrl) + if err != nil { + return nil, err + } + if list == nil || len(list) == 0 { + // Fetch sentry issues if no events found + requestUrl = prepareURLWithParams(cfg, sessionID, false) + list, err = makeRequest(cfg, requestUrl) + if err != nil { + return nil, err + } + if list == nil || len(list) == 0 { + return nil, fmt.Errorf("no logs found") + } + } + result, err := json.Marshal(list) + if err != nil { + return nil, err + } + return result, nil +} + +func parseSentryConfig(credentials interface{}) (sentryConfig, error) { cfg, ok := credentials.(sentryConfig) if !ok { strCfg, ok := credentials.(map[string]interface{}) if !ok { - return nil, fmt.Errorf("invalid credentials") + return cfg, fmt.Errorf("invalid credentials") } cfg = sentryConfig{} if val, ok := strCfg["organization_slug"].(string); ok { @@ -52,29 +81,37 @@ func (s *sentryClient) FetchSessionData(credentials interface{}, sessionID uint6 } } if cfg.URL == "" { - cfg.URL = "https://sentry.io" // Default to hosted Sentry if not specified + cfg.URL = "https://sentry.io" } - requestUrl := fmt.Sprintf("%s/api/0/projects/%s/%s/issues/", cfg.URL, cfg.OrganizationSlug, cfg.ProjectSlug) + return cfg, nil +} - testCallLimit := 1 +func prepareURLWithParams(cfg sentryConfig, sessionID uint64, isEvents bool) string { + entities := "issues" + if isEvents { + entities = "events" + } + requestUrl := fmt.Sprintf("%s/api/0/projects/%s/%s/%s/", cfg.URL, cfg.OrganizationSlug, cfg.ProjectSlug, entities) params := url.Values{} - if sessionID != 0 { - params.Add("query", fmt.Sprintf("openReplaySession.id:%d", sessionID)) - } else { - params.Add("per_page", fmt.Sprintf("%d", testCallLimit)) + querySign := ":" + if isEvents { + querySign = "=" } - requestUrl += "?" + params.Encode() + if sessionID != 0 { + params.Add("query", fmt.Sprintf("openReplaySession.id%s%d", querySign, sessionID)) + } else { + params.Add("per_page", "1") + } + return requestUrl + "?" + params.Encode() +} - // Create a new request +func makeRequest(cfg sentryConfig, requestUrl string) ([]SentryEvent, error) { req, err := http.NewRequest("GET", requestUrl, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %v", err) } - - // Add Authorization header req.Header.Set("Authorization", "Bearer "+cfg.Token) - // Send the request client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -82,30 +119,19 @@ func (s *sentryClient) FetchSessionData(credentials interface{}, sessionID uint6 } defer resp.Body.Close() - // Check if the response status is OK if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to fetch logs, status code: %v", resp.StatusCode) } - // Read the response body body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %v", err) } - // Parse the JSON response var events []SentryEvent err = json.Unmarshal(body, &events) if err != nil { return nil, fmt.Errorf("failed to parse JSON: %v", err) } - if events == nil || len(events) == 0 { - return nil, fmt.Errorf("no logs found") - } - - result, err := json.Marshal(events) - if err != nil { - return nil, err - } - return result, nil + return events, nil } diff --git a/ee/assist/package-lock.json b/ee/assist/package-lock.json index b25dd04f7..fa4de2957 100644 --- a/ee/assist/package-lock.json +++ b/ee/assist/package-lock.json @@ -9,6 +9,7 @@ "version": "v1.12.0-ee", "license": "Elastic License 2.0 (ELv2)", "dependencies": { + "@fastify/deepmerge": "^2.0.1", "@maxmind/geoip2-node": "^4.2.0", "@socket.io/redis-adapter": "^8.2.1", "express": "^4.21.1", @@ -39,6 +40,21 @@ "kuler": "^2.0.0" } }, + "node_modules/@fastify/deepmerge": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.1.tgz", + "integrity": "sha512-hx+wJQr9Ph1hY/dyzY0SxqjumMyqZDlIF6oe71dpRKDHUg7dFQfjG94qqwQ274XRjmUrwKiYadex8XplNHx3CA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/@maxmind/geoip2-node": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-4.2.0.tgz", diff --git a/ee/assist/package.json b/ee/assist/package.json index 74fd0374d..41f4bdbe0 100644 --- a/ee/assist/package.json +++ b/ee/assist/package.json @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/openreplay/openreplay#readme", "dependencies": { + "@fastify/deepmerge": "^2.0.1", "@maxmind/geoip2-node": "^4.2.0", "@socket.io/redis-adapter": "^8.2.1", "express": "^4.21.1", diff --git a/ee/assist/utils/helper-ee.js b/ee/assist/utils/helper-ee.js index 2700d5f32..4abe98b2a 100644 --- a/ee/assist/utils/helper-ee.js +++ b/ee/assist/utils/helper-ee.js @@ -28,7 +28,7 @@ const getBodyFromUWSResponse = async function (res) { const extractProjectKeyFromRequest = function (req) { if (process.env.uws === "true") { if (req.getParameter(0)) { - debug && console.log(`[WS]where projectKey=${req.getParameter(0)}`); + logger.debug(`[WS]where projectKey=${req.getParameter(0)}`); return req.getParameter(0); } } else { @@ -39,7 +39,7 @@ const extractProjectKeyFromRequest = function (req) { const extractSessionIdFromRequest = function (req) { if (process.env.uws === "true") { if (req.getParameter(1)) { - debug && console.log(`[WS]where projectKey=${req.getParameter(1)}`); + logger.debug(`[WS]where projectKey=${req.getParameter(1)}`); return req.getParameter(1); } } else { @@ -54,15 +54,15 @@ const extractPayloadFromRequest = async function (req, res) { }; if (process.env.uws === "true") { if (req.getQuery("q")) { - debug && console.log(`[WS]where q=${req.getQuery("q")}`); + logger.debug(`[WS]where q=${req.getQuery("q")}`); filters.query.value = req.getQuery("q"); } if (req.getQuery("key")) { - debug && console.log(`[WS]where key=${req.getQuery("key")}`); + logger.debug(`[WS]where key=${req.getQuery("key")}`); filters.query.key = req.getQuery("key"); } if (req.getQuery("userId")) { - debug && console.log(`[WS]where userId=${req.getQuery("userId")}`); + logger.debug(`[WS]where userId=${req.getQuery("userId")}`); filters.filter.userID = [req.getQuery("userId")]; } if (!filters.query.value) { @@ -88,7 +88,7 @@ const extractPayloadFromRequest = async function (req, res) { } filters.filter = helper.objectToObjectOfArrays(filters.filter); filters.filter = helper.transformFilters(filters.filter); - debug && console.log("payload/filters:" + JSON.stringify(filters)) + logger.debug("payload/filters:" + JSON.stringify(filters)) return Object.keys(filters).length > 0 ? filters : undefined; } const getAvailableRooms = async function (io) { diff --git a/frontend/app/components/Assist/AssistView.tsx b/frontend/app/components/Assist/AssistView.tsx index 37919238d..122ffcee3 100644 --- a/frontend/app/components/Assist/AssistView.tsx +++ b/frontend/app/components/Assist/AssistView.tsx @@ -2,8 +2,10 @@ import React from 'react'; import LiveSessionList from 'Shared/LiveSessionList'; import LiveSessionSearch from 'Shared/LiveSessionSearch'; import AssistSearchActions from './AssistSearchActions'; +import usePageTitle from '@/hooks/usePageTitle'; function AssistView() { + usePageTitle('Co-Browse - OpenReplay'); return (
diff --git a/frontend/app/components/FFlags/FFlagsList.tsx b/frontend/app/components/FFlags/FFlagsList.tsx index e73469809..59f359c8d 100644 --- a/frontend/app/components/FFlags/FFlagsList.tsx +++ b/frontend/app/components/FFlags/FFlagsList.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { numberWithCommas } from 'App/utils'; -import withPageTitle from 'HOCs/withPageTitle'; import withPermissions from 'HOCs/withPermissions'; import FFlagsListHeader from 'Components/FFlags/FFlagsListHeader'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; @@ -9,8 +8,10 @@ import FFlagItem from './FFlagItem'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import Select from 'Shared/Select'; +import usePageTitle from '@/hooks/usePageTitle'; function FFlagsList({ siteId }: { siteId: string }) { + usePageTitle('Feature Flags - OpenReplay'); const { featureFlagsStore, userStore } = useStore(); React.useEffect(() => { @@ -31,7 +32,7 @@ function FFlagsList({ siteId }: { siteId: string }) { options={[ { label: 'All', value: '0' as const }, { label: 'Enabled', value: '1' as const }, - { label: 'Disabled', value: '2' as const }, + { label: 'Disabled', value: '2' as const } ]} defaultValue={featureFlagsStore.activity} plain @@ -45,7 +46,7 @@ function FFlagsList({ siteId }: { siteId: string }) {