Merge branch 'dev' into live-se-red

This commit is contained in:
nick-delirium 2025-01-06 16:47:07 +01:00
commit db88d039ff
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
20 changed files with 233 additions and 57 deletions

View file

@ -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();

View file

@ -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
}

View file

@ -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",

View file

@ -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",

View file

@ -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) {

View file

@ -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 (
<div className="w-full mx-auto" style={{ maxWidth: '1360px'}}>
<AssistSearchActions />

View file

@ -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 }) {
<Select
options={[
{ label: 'Newest', value: 'DESC' },
{ label: 'Oldest', value: 'ASC' },
{ label: 'Oldest', value: 'ASC' }
]}
defaultValue={featureFlagsStore.sort.order}
plain
@ -65,7 +66,7 @@ function FFlagsList({ siteId }: { siteId: string }) {
<AnimatedSVG name={ICONS.NO_FFLAGS} size={60} />
<div className="text-center mt-4 text-lg font-medium">
{featureFlagsStore.sort.query === ''
? "You haven't created any feature flags yet"
? 'You haven\'t created any feature flags yet'
: 'No matching results'}
</div>
</div>
@ -121,6 +122,4 @@ function FFlagsList({ siteId }: { siteId: string }) {
);
}
export default withPageTitle('Feature Flags')(
withPermissions(['FEATURE_FLAGS'])(observer(FFlagsList))
);
export default withPermissions(['FEATURE_FLAGS'])(observer(FFlagsList));

View file

@ -10,6 +10,7 @@ import FlagView from 'Components/FFlags/FlagView/FlagView';
import { observer } from 'mobx-react-lite';
import { useStore } from '@/mstore';
import NotesList from 'Shared/SessionsTabOverview/components/Notes/NoteList';
import Bookmarks from 'Shared/SessionsTabOverview/components/Bookmarks/Bookmarks';
// @ts-ignore
interface IProps extends RouteComponentProps {
@ -34,11 +35,17 @@ function Overview({ match: { params } }: IProps) {
return (
<Switch>
<Route exact strict
path={[withSiteId(sessions(), siteId), withSiteId(bookmarks(), siteId)]}>
path={withSiteId(sessions(), siteId)}>
<div className="mb-5 w-full mx-auto" style={{ maxWidth: '1360px' }}>
<SessionsTabOverview />
</div>
</Route>
<Route exact strict
path={withSiteId(bookmarks(), siteId)}>
<div className="mb-5 w-full mx-auto" style={{ maxWidth: '1360px' }}>
<Bookmarks />
</div>
</Route>
<Route exact strict path={withSiteId(notes(), siteId)}>
<div className="mb-5 w-full mx-auto" style={{ maxWidth: '1360px' }}>
<NotesList />

View file

@ -9,12 +9,14 @@ import { observer } from 'mobx-react-lite';
import NoSessionsMessage from 'Shared/NoSessionsMessage/NoSessionsMessage';
import MainSearchBar from 'Shared/MainSearchBar/MainSearchBar';
import SearchActions from "../SearchActions";
import usePageTitle from '@/hooks/usePageTitle';
function SessionsTabOverview() {
const [query, setQuery] = React.useState('');
const { aiFiltersStore, searchStore } = useStore();
const appliedFilter = searchStore.instance;
const activeTab = searchStore.activeTab;
usePageTitle('Sessions - OpenReplay');
const handleKeyDown = (event: any) => {
if (event.key === 'Enter') {

View file

@ -0,0 +1,78 @@
import React, { useEffect } from 'react';
import SessionList from 'Shared/SessionsTabOverview/components/SessionList/SessionList';
import NoteTags from 'Shared/SessionsTabOverview/components/Notes/NoteTags';
import { Loader, NoContent, Pagination } from 'UI';
import AnimatedSVG from 'Shared/AnimatedSVG';
import { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import NoteItem from 'Shared/SessionsTabOverview/components/Notes/NoteItem';
import { useStore } from '@/mstore';
import { observer } from 'mobx-react-lite';
import SessionItem from 'Shared/SessionItem/SessionItem';
import usePageTitle from '@/hooks/usePageTitle';
function Bookmarks() {
const { projectsStore, sessionStore, customFieldStore, userStore, searchStore } = useStore();
const isEnterprise = userStore.isEnterprise;
const isLoggedIn = userStore.isLoggedIn;
const bookmarks = sessionStore.bookmarks;
usePageTitle('Bookmarks - OpenReplay');
useEffect(() => {
void sessionStore.fetchBookmarkedSessions();
}, []);
return (
<div className="widget-wrapper">
<div className="flex items-center px-4 py-1 justify-between w-full">
<h2 className="text-2xl capitalize mr-4">Bookmarks</h2>
</div>
<div className="border-b" />
<Loader loading={bookmarks.loading}>
<NoContent
show={bookmarks.list.length === 0}
title={
<div className="flex flex-col items-center justify-center">
{/* <Icon name="no-dashboard" size={80} color="figmaColors-accent-secondary" /> */}
<AnimatedSVG name={ICONS.NO_BOOKMARKS} size={60} />
<div className="text-center mt-4 text-lg font-medium">No sessions bookmarked</div>
</div>
}
>
<div className="border-b rounded bg-white">
{bookmarks.list.map((session: any) => (
<div key={session.sessionId} className="border-b">
<SessionItem
session={session}
hasUserFilter={false}
// onUserClick={() => {}}
// metaList={metaList}
// lastPlayedSessionId={lastPlayedSessionId}
bookmarked={true}
// toggleFavorite={toggleFavorite}
/>
</div>
))}
</div>
<div className="w-full flex items-center justify-between py-4 px-6">
<div className="text-disabled-text">
Showing{' '}
<span className="font-semibold">{Math.min(bookmarks.list.length, bookmarks.pageSize)}</span> out
of <span className="font-semibold">{bookmarks.total}</span> sessions.
</div>
<Pagination
page={bookmarks.page}
total={bookmarks.total}
onPageChange={(page) => sessionStore.updateBookmarksPage(page)}
limit={bookmarks.pageSize}
debounceRequest={100}
/>
</div>
</NoContent>
</Loader>
</div>
);
}
export default observer(Bookmarks);

View file

@ -6,8 +6,10 @@ import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import NoteTags from 'Shared/SessionsTabOverview/components/Notes/NoteTags';
import usePageTitle from '@/hooks/usePageTitle';
function NotesList() {
usePageTitle('Notes - OpenReplay');
const { notesStore } = useStore();
React.useEffect(() => {

View file

@ -362,6 +362,7 @@ class SearchStore {
if (this.activeTags[0] && this.activeTags[0] !== 'all') {
const tagFilter = filtersMap[FilterKey.ISSUE];
tagFilter.type = tagFilter.type.toLowerCase();
tagFilter.value = [
issues_types.find((i: any) => i.type === this.activeTags[0])?.type,
];

View file

@ -94,12 +94,19 @@ class DevTools {
}
}
interface Bookmarks {
list: Session[];
page: number;
total: number;
pageSize: number;
loading: boolean;
}
export default class SessionStore {
userFilter: UserFilter = new UserFilter();
devTools: DevTools = new DevTools();
list: Session[] = [];
bookmarks: Bookmarks = { list: [], page: 1, total: 0, pageSize: 10, loading: false };
sessionIds: string[] = [];
current = new Session();
total = 0;
@ -529,9 +536,33 @@ export default class SessionStore {
this.list = [];
this.total = 0;
this.sessionIds = [];
this.bookmarks = { list: [], page: 1, total: 0, pageSize: 10, loading: false };
}
setLastPlayedSessionId = (sessionId: string) => {
this.lastPlayedSessionId = sessionId;
}
async fetchBookmarkedSessions() {
try {
this.bookmarks.loading = true;
const params = {
page: this.bookmarks.page,
limit: this.bookmarks.pageSize,
bookmarked: true,
}
const data = await sessionService.getSessions(params);
this.bookmarks.list = data.sessions.map((s: any) => new Session(s));
this.bookmarks.total = data.total;
} catch (e) {
console.error(e);
} finally {
this.bookmarks.loading = false;
}
}
updateBookmarksPage(page: number) {
this.bookmarks.page = page;
void this.fetchBookmarkedSessions();
}
}

View file

@ -25,7 +25,7 @@
"@ant-design/icons": "^5.2.5",
"@babel/plugin-transform-private-methods": "^7.23.3",
"@floating-ui/react-dom-interactions": "^0.10.3",
"@medv/finder": "^3.1.0",
"@medv/finder": "^4.0.2",
"@sentry/browser": "^5.21.1",
"@svg-maps/world": "^1.0.1",
"@tanstack/react-query": "^5.56.2",
@ -75,7 +75,7 @@
"recharts": "^2.12.7",
"socket.io-client": "^4.4.1",
"syncod": "^0.0.1",
"virtua": "^0.35.1"
"virtua": "^0.39.2"
},
"devDependencies": {
"@babel/cli": "^7.23.0",

View file

@ -2549,10 +2549,10 @@ __metadata:
languageName: node
linkType: hard
"@medv/finder@npm:^3.1.0":
version: 3.2.0
resolution: "@medv/finder@npm:3.2.0"
checksum: 10c1/5088ae315138074fa168c51d4092a17ab962c85fbb7de97bbce396adc99a525b2d9c4d92231419bbd0ee13944f16c39672d81a1d2bce3e9cbeac527cfb6790f0
"@medv/finder@npm:^4.0.2":
version: 4.0.2
resolution: "@medv/finder@npm:4.0.2"
checksum: 10c1/067a99e0ce8ddecebd554edf36d6211bc630d6cae351ed93b2110c450f2bbea4852887a2978c2f94f6319d7aa055aeb8723e1514e9cc512faa628627930773b2
languageName: node
linkType: hard
@ -11582,7 +11582,7 @@ __metadata:
"@babel/runtime": "npm:^7.23.2"
"@floating-ui/react-dom-interactions": "npm:^0.10.3"
"@jest/globals": "npm:^29.7.0"
"@medv/finder": "npm:^3.1.0"
"@medv/finder": "npm:^4.0.2"
"@openreplay/sourcemap-uploader": "npm:^3.0.10"
"@sentry/browser": "npm:^5.21.1"
"@svg-maps/world": "npm:^1.0.1"
@ -11683,7 +11683,7 @@ __metadata:
ts-jest: "npm:^29.0.5"
ts-node: "npm:^10.7.0"
typescript: "npm:^4.6.4"
virtua: "npm:^0.35.1"
virtua: "npm:^0.39.2"
webpack: "npm:^5.96.0"
webpack-cli: "npm:^5.1.4"
webpack-dev-server: "npm:^5.1.0"
@ -16255,14 +16255,14 @@ __metadata:
languageName: node
linkType: hard
"virtua@npm:^0.35.1":
version: 0.35.1
resolution: "virtua@npm:0.35.1"
"virtua@npm:^0.39.2":
version: 0.39.2
resolution: "virtua@npm:0.39.2"
peerDependencies:
react: ">=16.14.0"
react-dom: ">=16.14.0"
solid-js: ">=1.0"
svelte: ">=4.0"
svelte: ">=5.0"
vue: ">=3.2"
peerDependenciesMeta:
react:
@ -16275,7 +16275,7 @@ __metadata:
optional: true
vue:
optional: true
checksum: 10c1/ea747bb9a3b6c41cfa08d2d57d7287224e4bbdf815dcf8736f310372df83472c9bbc4b8286341a8b5bc9d9065baf49ded5233b6013b877fbe1f00a8fc9a6ae0c
checksum: 10c1/4aae200bea816a167beb2ea0985be1dc6ac6da4d9410bd7d64af816ae4e41bf15d84bdb0f0bbb636b9d8d9ff01f72d6d73c1f38ff80f86143600dc1451ecf76d
languageName: node
linkType: hard

View file

@ -90,7 +90,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation("com.facebook.react:react-native:0.20.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
implementation("com.github.openreplay:android-tracker:v1.1.6")
implementation("com.github.openreplay:android-tracker:v1.1.7")
}

View file

@ -116,4 +116,9 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
duration = durationULong
)
}
@ReactMethod
func sendMessage(type: String, message: String) {
OpenReplay.sendMessage(type: type, message: message)
}
}

View file

@ -1,3 +1,7 @@
## 15.0.5
- update medv/finder to 4.0.2 for better support of css-in-js libs
## 15.0.4
- support for spritemaps (svg with `use` tags)

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package",
"version": "15.0.4",
"version": "15.0.5",
"keywords": [
"logging",
"replay"
@ -70,7 +70,7 @@
"typescript": "^5.6.3"
},
"dependencies": {
"@medv/finder": "^3.2.0",
"@medv/finder": "^4.0.2",
"@openreplay/network-proxy": "^1.0.5",
"error-stack-parser": "^2.0.6",
"error-stack-parser-es": "^0.1.5",