From 179dbd22d5afd197f163b860d6823aa6a9506c60 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 24 Jun 2022 20:48:06 +0200 Subject: [PATCH 01/25] fix(ui) - roles and permissions --- frontend/app/api_client.js | 1 - frontend/app/components/Client/Roles/Roles.tsx | 6 +++--- .../Client/Roles/components/RoleForm/RoleForm.tsx | 10 +++++----- .../Client/Roles/components/RoleItem/RoleItem.tsx | 7 +++---- .../Users/components/AddUserButton/AddUserButton.tsx | 1 + frontend/app/mstore/userStore.ts | 2 ++ frontend/app/services/UserService.ts | 7 ++++--- frontend/app/types/account/account.js | 1 + 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 918fe57e2..1f85d5af9 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -25,7 +25,6 @@ const siteIdRequiredPaths = [ '/custom_metrics', '/dashboards', '/metrics', - '/trails', // '/custom_metrics/sessions', ]; diff --git a/frontend/app/components/Client/Roles/Roles.tsx b/frontend/app/components/Client/Roles/Roles.tsx index 831477b90..9d9a27cec 100644 --- a/frontend/app/components/Client/Roles/Roles.tsx +++ b/frontend/app/components/Client/Roles/Roles.tsx @@ -110,9 +110,9 @@ function Roles(props: Props) { >
-
Title
-
Project Access
-
Feature Access
+
Title
+
Project Access
+
Feature Access
{roles.map(role => ( { isSearchable name="projects" options={ projectOptions } - onChange={ ({ value }: any) => writeOption({ name: 'projects', value }) } + onChange={ ({ value }: any) => writeOption({ name: 'projects', value: value.value }) } value={null} /> { role.projects.size > 0 && ( @@ -181,10 +181,10 @@ export default connect((state: any) => { key: p.get('id'), value: p.get('id'), label: p.get('name'), - isDisabled: role.projects.includes(p.get('id')), - })).toJS(), - permissions: state.getIn(['roles', 'permissions']) - .map(({ text, value }: any) => ({ label: text, value, isDisabled: role.permissions.includes(value) })).toJS(), + // isDisabled: role.projects.includes(p.get('id')), + })).filter(({ value }: any) => !role.projects.includes(value)).toJS(), + permissions: state.getIn(['roles', 'permissions']).filter(({ value }: any) => !role.permissions.includes(value)) + .map(({ text, value }: any) => ({ label: text, value })).toJS(), saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]), projectsMap: projects.reduce((acc: any, p: any) => { acc[ p.get('id') ] = p.get('name') diff --git a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx index c07919e8e..a5a9d1b61 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx +++ b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx @@ -45,17 +45,16 @@ function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, proj {role.permissions.map((permission: any) => ( ))} -
- { isAdmin && (
- { !!editHandler && + {isAdmin && !!editHandler &&
editHandler(role) }>
}
- )} + + ); } diff --git a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx index 6907a7b12..5632ed6fa 100644 --- a/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx +++ b/frontend/app/components/Client/Users/components/AddUserButton/AddUserButton.tsx @@ -9,6 +9,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; function AddUserButton({ isAdmin = false, onClick }: any ) { const { userStore } = useStore(); const limtis = useObserver(() => userStore.limits); + console.log('limtis', limtis) const cannAddUser = useObserver(() => isAdmin && (limtis.teamMember === -1 || limtis.teamMember > 0)); return ( { this.saving = false; + toast.error('Error saving user'); reject(error); }).finally(() => { this.saving = false; @@ -130,6 +131,7 @@ export default class UserStore { resolve(response); }).catch(error => { this.saving = false; + toast.error('Error deleting user'); reject(error); }).finally(() => { this.saving = false; diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts index 43c6350f2..5ab27bd30 100644 --- a/frontend/app/services/UserService.ts +++ b/frontend/app/services/UserService.ts @@ -1,5 +1,6 @@ import APIClient from 'App/api_client'; import { IUser } from 'App/mstore/types/user' +import { fetchErrorCheck } from 'App/utils' export default class UserService { private client: APIClient; @@ -28,11 +29,11 @@ export default class UserService { const data = user.toSave(); if (user.userId) { return this.client.put('/client/members/' + user.userId, data) - .then((response: { json: () => any; }) => response.json()) + .then(fetchErrorCheck) .then((response: { data: any; }) => response.data || {}) } else { return this.client.post('/client/members', data) - .then((response: { json: () => any; }) => response.json()) + .then(fetchErrorCheck) .then((response: { data: any; }) => response.data || {}); } } @@ -45,7 +46,7 @@ export default class UserService { delete(userId: string) { return this.client.delete('/client/members/' + userId) - .then((response: { json: () => any; }) => response.json()) + .then(fetchErrorCheck) .then((response: { data: any; }) => response.data || {}); } diff --git a/frontend/app/types/account/account.js b/frontend/app/types/account/account.js index e130c9c0a..ebc70bae8 100644 --- a/frontend/app/types/account/account.js +++ b/frontend/app/types/account/account.js @@ -18,6 +18,7 @@ export default Member.extend({ apiKey: undefined, tenantKey: undefined, tenantName: undefined, + edition: undefined, }, { fromJS: ({ ...account})=> ({ ...account, From 95c9b6e3f56497f516e7ba5aadb20f5822230b28 Mon Sep 17 00:00:00 2001 From: Alexander Zavorotynskiy Date: Mon, 27 Jun 2022 10:35:05 +0200 Subject: [PATCH 02/25] feat(backend): minor fixes after prerelease tests --- backend/internal/http/router/handlers-web.go | 8 +- backend/pkg/db/postgres/messages-web-stats.go | 16 +- backend/pkg/messages/batch.go | 3 +- backend/pkg/sessions/builder.go | 2 +- .../internal/{ => db}/datasaver/stats.go | 0 ee/backend/pkg/db/clickhouse/connector.go | 98 +------ ee/backend/pkg/db/clickhouse/messages-ios.go | 240 +++++++++--------- 7 files changed, 142 insertions(+), 225 deletions(-) rename ee/backend/internal/{ => db}/datasaver/stats.go (100%) diff --git a/backend/internal/http/router/handlers-web.go b/backend/internal/http/router/handlers-web.go index 69cbe4675..a8b47c559 100644 --- a/backend/internal/http/router/handlers-web.go +++ b/backend/internal/http/router/handlers-web.go @@ -118,10 +118,14 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) } // Save sessionStart to db - e.services.Database.InsertWebSessionStart(sessionID, sessionStart) + if err := e.services.Database.InsertWebSessionStart(sessionID, sessionStart); err != nil { + log.Printf("can't insert session start: %s", err) + } // Send sessionStart message to kafka - e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart)) + if err := e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart)); err != nil { + log.Printf("can't send session start: %s", err) + } } ResponseWithJSON(w, &StartSessionResponse{ diff --git a/backend/pkg/db/postgres/messages-web-stats.go b/backend/pkg/db/postgres/messages-web-stats.go index 2a5a11750..396f2e74d 100644 --- a/backend/pkg/db/postgres/messages-web-stats.go +++ b/backend/pkg/db/postgres/messages-web-stats.go @@ -1,7 +1,6 @@ package postgres import ( - "log" . "openreplay/backend/pkg/messages" "openreplay/backend/pkg/url" ) @@ -28,25 +27,16 @@ func (conn *Conn) InsertWebStatsPerformance(sessionID uint64, p *PerformanceTrac $10, $11, $12, $13, $14, $15 )` - //conn.batchQueue(sessionID, sqlRequest, - // sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id - // p.MinFPS, p.AvgFPS, p.MaxFPS, - // p.MinCPU, p.AvgCPU, p.MinCPU, - // p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize, - // p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize, - //) - if err := conn.exec(sqlRequest, + conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id p.MinFPS, p.AvgFPS, p.MaxFPS, p.MinCPU, p.AvgCPU, p.MinCPU, p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize, p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize, - ); err != nil { - log.Printf("can't insert perf: %s", err) - } + ) // Record approximate message size - //conn.updateBatchSize(sessionID, len(sqlRequest)+8*15) + conn.updateBatchSize(sessionID, len(sqlRequest)+8*15) return nil } diff --git a/backend/pkg/messages/batch.go b/backend/pkg/messages/batch.go index 2d531c429..84f6cde99 100644 --- a/backend/pkg/messages/batch.go +++ b/backend/pkg/messages/batch.go @@ -40,7 +40,8 @@ func ReadBatchReader(reader io.Reader, messageHandler func(Message)) error { // No skipping here for making it easy to encode back the same sequence of message // continue readLoop case *SessionStart: - // Save session start timestamp for collecting "empty" sessions + timestamp = int64(m.Timestamp) + case *SessionEnd: timestamp = int64(m.Timestamp) } msg.Meta().Index = index diff --git a/backend/pkg/sessions/builder.go b/backend/pkg/sessions/builder.go index eed3d8229..c9cb0b6dd 100644 --- a/backend/pkg/sessions/builder.go +++ b/backend/pkg/sessions/builder.go @@ -50,7 +50,7 @@ func (b *builder) handleMessage(message Message, messageID uint64) { timestamp := GetTimestamp(message) if timestamp == 0 { switch message.(type) { - case *SessionEnd, *IssueEvent, *PerformanceTrackAggr: + case *IssueEvent, *PerformanceTrackAggr: break default: log.Printf("skip message with empty timestamp, sessID: %d, msgID: %d, msgType: %d", b.sessionID, messageID, message.TypeID()) diff --git a/ee/backend/internal/datasaver/stats.go b/ee/backend/internal/db/datasaver/stats.go similarity index 100% rename from ee/backend/internal/datasaver/stats.go rename to ee/backend/internal/db/datasaver/stats.go diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index 532389425..cc0d20497 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -9,23 +9,16 @@ import ( ) type Connector struct { - sessionsIOS *bulk - //viewsIOS *bulk - clicksIOS *bulk - inputsIOS *bulk - crashesIOS *bulk - performanceIOS *bulk - resourcesIOS *bulk - sessions *bulk - metadata *bulk // TODO: join sessions, sessions_metadata & sessions_ios - resources *bulk - pages *bulk - clicks *bulk - inputs *bulk - errors *bulk - performance *bulk - longtasks *bulk - db *sql.DB + sessions *bulk + metadata *bulk // TODO: join sessions, sessions_metadata & sessions_ios + resources *bulk + pages *bulk + clicks *bulk + inputs *bulk + errors *bulk + performance *bulk + longtasks *bulk + db *sql.DB } func NewConnector(url string) *Connector { @@ -37,35 +30,6 @@ func NewConnector(url string) *Connector { } return &Connector{ db: db, - // sessionsIOS: newBulk(db, ` - // INSERT INTO sessions_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, views_count, events_count, crashes_count, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // viewsIOS: newBulk(db, ` - // INSERT INTO views_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint, speed_index, visually_complete, time_to_interactive) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // clicksIOS: newBulk(db, ` - // INSERT INTO clicks_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, label) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // inputsIOS: newBulk(db, ` - // INSERT INTO inputs_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, label) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // crashesIOS: newBulk(db, ` - // INSERT INTO crashes_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, name, reason, crash_id) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // performanceIOS: newBulk(db, ` - // INSERT INTO performance_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_memory, avg_memory, max_memory) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - // `), - // resourcesIOS: newBulk(db, ` - // INSERT INTO resources_ios (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, url, duration, body_size, success, method, status) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, nullIf(?, ''), ?) - // `), - sessions: newBulk(db, ` INSERT INTO sessions (session_id, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, user_browser, user_browser_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) @@ -107,27 +71,6 @@ func NewConnector(url string) *Connector { } func (conn *Connector) Prepare() error { - // if err := conn.sessionsIOS.prepare(); err != nil { - // return err - // } - // // if err := conn.viewsIOS.prepare(); err != nil { - // // return err - // // } - // if err := conn.clicksIOS.prepare(); err != nil { - // return err - // } - // if err := conn.inputsIOS.prepare(); err != nil { - // return err - // } - // if err := conn.crashesIOS.prepare(); err != nil { - // return err - // } - // if err := conn.performanceIOS.prepare(); err != nil { - // return err - // } - // if err := conn.resourcesIOS.prepare(); err != nil { - // return err - // } if err := conn.sessions.prepare(); err != nil { return err } @@ -159,27 +102,6 @@ func (conn *Connector) Prepare() error { } func (conn *Connector) Commit() error { - // if err := conn.sessionsIOS.commit(); err != nil { - // return err - // } - // // if err := conn.viewsIOS.commit(); err != nil { - // // return err - // // } - // if err := conn.clicksIOS.commit(); err != nil { - // return err - // } - // if err := conn.inputsIOS.commit(); err != nil { - // return err - // } - // if err := conn.crashesIOS.commit(); err != nil { - // return err - // } - // if err := conn.performanceIOS.commit(); err != nil { - // return err - // } - // if err := conn.resourcesIOS.commit(); err != nil { - // return err - // } if err := conn.sessions.commit(); err != nil { return err } diff --git a/ee/backend/pkg/db/clickhouse/messages-ios.go b/ee/backend/pkg/db/clickhouse/messages-ios.go index f5ca30495..1c7723b2b 100644 --- a/ee/backend/pkg/db/clickhouse/messages-ios.go +++ b/ee/backend/pkg/db/clickhouse/messages-ios.go @@ -2,46 +2,41 @@ package clickhouse import ( "errors" - - "openreplay/backend/pkg/hashid" - "openreplay/backend/pkg/url" - . "openreplay/backend/pkg/db/types" - . "openreplay/backend/pkg/messages" ) - // TODO: join sessions & sessions_ios clcikhouse tables func (conn *Connector) InsertIOSSession(session *Session) error { - if (session.Duration == nil) { + if session.Duration == nil { return errors.New("Clickhouse: trying to insert session with ") } - return conn.sessionsIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(session.Timestamp), - uint32(*session.Duration), - session.PagesCount, - session.EventsCount, - session.ErrorsCount, - session.Metadata1, - session.Metadata2, - session.Metadata3, - session.Metadata4, - session.Metadata5, - session.Metadata6, - session.Metadata7, - session.Metadata8, - session.Metadata9, - session.Metadata10, - ) + //return conn.sessionsIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(session.Timestamp), + // uint32(*session.Duration), + // session.PagesCount, + // session.EventsCount, + // session.ErrorsCount, + // session.Metadata1, + // session.Metadata2, + // session.Metadata3, + // session.Metadata4, + // session.Metadata5, + // session.Metadata6, + // session.Metadata7, + // session.Metadata8, + // session.Metadata9, + // session.Metadata10, + //) + return nil } // func (conn *Connector) IOSScreenEnter(session *Session, msg *PageEvent) error { @@ -76,105 +71,110 @@ func (conn *Connector) InsertIOSClickEvent(session *Session, msg *IOSClickEvent) if msg.Label == "" { return nil } - return conn.clicksIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - ) + //return conn.clicksIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Label, + //) + return nil } func (conn *Connector) InsertIOSInputEvent(session *Session, msg *IOSInputEvent) error { if msg.Label == "" { return nil } - return conn.inputsIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Label, - ) + //return conn.inputsIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Label, + //) + return nil } func (conn *Connector) InsertIOSCrash(session *Session, msg *IOSCrash) error { - return conn.crashesIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - msg.Name, - msg.Reason, - hashid.IOSCrashID(session.ProjectID, msg), - ) + //return conn.crashesIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // msg.Name, + // msg.Reason, + // hashid.IOSCrashID(session.ProjectID, msg), + //) + return nil } func (conn *Connector) InsertIOSNetworkCall(session *Session, msg *IOSNetworkCall) error { - return conn.resourcesIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(msg.Timestamp), - url.DiscardURLQuery(msg.URL), - nullableUint16(uint16(msg.Duration)), - nullableUint32(uint32(len(msg.Body))), - msg.Success, - url.EnsureMethod(msg.Method), // nullableString causes error "unexpected type *string" - nullableUint16(uint16(msg.Status)), - ) + //return conn.resourcesIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(msg.Timestamp), + // url.DiscardURLQuery(msg.URL), + // nullableUint16(uint16(msg.Duration)), + // nullableUint32(uint32(len(msg.Body))), + // msg.Success, + // url.EnsureMethod(msg.Method), // nullableString causes error "unexpected type *string" + // nullableUint16(uint16(msg.Status)), + //) + return nil } func (conn *Connector) InsertIOSPerformanceAggregated(session *Session, msg *IOSPerformanceAggregated) error { - var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 - return conn.performanceIOS.exec( - session.SessionID, - session.ProjectID, - session.TrackerVersion, - nullableString(session.RevID), - session.UserUUID, - session.UserOS, - nullableString(session.UserOSVersion), - nullableString(session.UserDevice), - session.UserDeviceType, - session.UserCountry, - datetime(timestamp), - uint8(msg.MinFPS), - uint8(msg.AvgFPS), - uint8(msg.MaxFPS), - uint8(msg.MinCPU), - uint8(msg.AvgCPU), - uint8(msg.MaxCPU), - msg.MinMemory, - msg.AvgMemory, - msg.MaxMemory, - ) + //var timestamp uint64 = (msg.TimestampStart + msg.TimestampEnd) / 2 + //return conn.performanceIOS.exec( + // session.SessionID, + // session.ProjectID, + // session.TrackerVersion, + // nullableString(session.RevID), + // session.UserUUID, + // session.UserOS, + // nullableString(session.UserOSVersion), + // nullableString(session.UserDevice), + // session.UserDeviceType, + // session.UserCountry, + // datetime(timestamp), + // uint8(msg.MinFPS), + // uint8(msg.AvgFPS), + // uint8(msg.MaxFPS), + // uint8(msg.MinCPU), + // uint8(msg.AvgCPU), + // uint8(msg.MaxCPU), + // msg.MinMemory, + // msg.AvgMemory, + // msg.MaxMemory, + //) + return nil } From 8ae15c799e5a728a911416fba22121f7de9bfbde Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 27 Jun 2022 12:04:26 +0200 Subject: [PATCH 03/25] fix(ui): fix jira integration, fix widget name esc handling, minor fixes --- .../components/WidgetName/WidgetName.tsx | 4 +- frontend/app/components/Session/WebPlayer.js | 5 +- .../app/components/Session_/Issues/Issues.js | 82 ++++++------------- .../components/Session_/Issues/IssuesModal.js | 7 +- .../components/Session_/PlayerBlockHeader.js | 2 +- frontend/tsconfig.json | 1 + 6 files changed, 38 insertions(+), 63 deletions(-) diff --git a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx index 7c9c62439..52beded51 100644 --- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx +++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx @@ -43,10 +43,10 @@ function WidgetName(props: Props) { setEditing(false) } } - document.addEventListener('keypress', handler, false) + document.addEventListener('keydown', handler, false) return () => { - document.removeEventListener('keypress', handler, false) + document.removeEventListener('keydown', handler, false) } }, []) diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index c0a8442be..f1bcd2ddb 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { Loader } from 'UI'; import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; +import { fetchList } from 'Duck/integrations'; import { PlayerProvider, connectPlayer, @@ -40,7 +41,7 @@ function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { } function WebPlayer (props) { - const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, config } = props; + const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; const TABS = { EVENTS: 'Events', @@ -50,6 +51,7 @@ function WebPlayer (props) { const [activeTab, setActiveTab] = useState(''); useEffect(() => { + fetchList('issues') initPlayer(session, jwt); const jumptTime = props.query.get('jumpto'); @@ -89,4 +91,5 @@ export default connect(state => ({ }), { toggleFullscreen, closeBottomBlock, + fetchList, })(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session_/Issues/Issues.js b/frontend/app/components/Session_/Issues/Issues.js index a6f6dd077..a5c7e1a61 100644 --- a/frontend/app/components/Session_/Issues/Issues.js +++ b/frontend/app/components/Session_/Issues/Issues.js @@ -1,12 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Popup, Button } from 'UI'; +import { Popup, Button, Icon } from 'UI'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import IssuesModal from './IssuesModal'; import { fetchProjects, fetchMeta } from 'Duck/assignments'; -import withToggle from 'HOCs/withToggle'; import stl from './issues.module.css'; -import { fetchList as fetchListIntegration } from 'Duck/integrations/actions'; @connect(state => ({ issues: state.getIn(['assignments', 'list']), @@ -21,16 +19,10 @@ import { fetchList as fetchListIntegration } from 'Duck/integrations/actions'; jiraConfig: state.getIn([ 'issues', 'list' ]).first(), issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]), -}), { fetchMeta, fetchProjects, fetchListIntegration }) -@withToggle('isModalDisplayed', 'toggleModal') +}), { fetchMeta, fetchProjects }) class Issues extends React.Component { state = {showModal: false }; - componentDidMount() { - if (!this.props.issuesFetched) - this.props.fetchListIntegration('issues') - } - constructor(props) { super(props); this.state = { showModal: false }; @@ -46,12 +38,7 @@ class Issues extends React.Component { this.setState({ showModal: true }); } - handleClose = () => { - this.setState({ showModal: false }); - } - handleOpen = () => { - alert('test') this.setState({ showModal: true }); if (!this.props.projectsFetched) { // cache projects fetch this.props.fetchProjects().then(function() { @@ -67,52 +54,33 @@ class Issues extends React.Component { const { sessionId, isModalDisplayed, projectsLoading, metaLoading, fetchIssuesLoading, issuesIntegration } = this.props; - const { showModal } = this.state; const provider = issuesIntegration.provider return ( -
-
- - - Create Issue -
- } - on="click" - position="top right" - content={ - - - - } - // trigger="click" - theme="tippy-light" - > - { - - } - +
+
+ + + + } + theme="tippy-light" + > +
+ + Create Issue +
+
+
-
); } }; diff --git a/frontend/app/components/Session_/Issues/IssuesModal.js b/frontend/app/components/Session_/Issues/IssuesModal.js index a1198f654..3e45bb1b1 100644 --- a/frontend/app/components/Session_/Issues/IssuesModal.js +++ b/frontend/app/components/Session_/Issues/IssuesModal.js @@ -1,7 +1,8 @@ import React from 'react'; import stl from './issuesModal.module.css'; import IssueForm from './IssueForm'; -import { Icon } from 'UI'; +import { Provider } from 'react-redux'; +import store from 'App/store'; const IssuesModal = ({ sessionId, @@ -14,7 +15,9 @@ const IssuesModal = ({ {/* */} {`Report an Issue on ${provider === 'jira' ? 'Jira' : 'Github'}`} - + + + ); } diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js index 7f9aed6c7..d652ed2ec 100644 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ b/frontend/app/components/Session_/PlayerBlockHeader.js @@ -138,7 +138,7 @@ export default class PlayerBlockHeader extends React.PureComponent { tabs={ TABS } active={ activeTab } onClick={ (tab) => { setActiveTab(tab); !showEvents && toggleEvents(true) } } - border={ true } + border={ false } /> )} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 437f453c4..f749f1bd3 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -25,6 +25,7 @@ "Player": ["./app/player"], "HOCs/*": ["./app/components/hocs/*"], "Types/*": ["./app/types/*"], + "Duck/*": ["./app/duck/*"], } }, "include": ["app"] From 6592f33827e166bc274ba2d19d6ddbf7ca378aec Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 27 Jun 2022 12:32:54 +0200 Subject: [PATCH 04/25] fix(ui): fix add outside click for modal, fix right menu headers --- .../DashboardEditModal/DashboardEditModal.tsx | 2 +- .../DashboardSelectionModal.tsx | 2 +- .../components/WidgetSessions/WidgetSessions.tsx | 6 +++--- .../Funnels/FunnelSaveModal/FunnelSaveModal.js | 2 +- .../Session_/PageInsightsPanel/PageInsightsPanel.tsx | 5 +++-- .../shared/SaveSearchModal/SaveSearchModal.tsx | 2 +- .../app/components/ui/Confirmation/Confirmation.js | 1 + frontend/app/components/ui/Modal/Modal.tsx | 10 +++++++++- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx index bf92d6ea1..da0e49935 100644 --- a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx @@ -29,7 +29,7 @@ function DashboardEditModal(props: Props) { const write = ({ target: { value, name } }) => dashboard.update({ [ name ]: value }) return useObserver(() => ( - +
{ 'Edit Dashboard' }
( - +
{ 'Add to selected dashboard' }
{filteredSessions.sessions.map((session: any) => ( - <> - + +
- + ))}
diff --git a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js index 6e324e8db..ca35d401d 100644 --- a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js +++ b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js @@ -46,7 +46,7 @@ export default class FunnelSaveModal extends React.PureComponent { } = this.props; return ( - +
{ 'Save Funnel' }
-
-
+
+
+ Clicks
+
{ 'Save Search' }
proceed(false)} > {header} diff --git a/frontend/app/components/ui/Modal/Modal.tsx b/frontend/app/components/ui/Modal/Modal.tsx index 94eef2248..2e4812400 100644 --- a/frontend/app/components/ui/Modal/Modal.tsx +++ b/frontend/app/components/ui/Modal/Modal.tsx @@ -5,6 +5,7 @@ interface Props { children: React.ReactNode; open?: boolean; size ?: 'tiny' | 'small' | 'large' | 'fullscreen'; + onClose?: () => void; } function Modal(props: Props) { const { children, open = false, size = 'small' } = props; @@ -28,10 +29,17 @@ function Modal(props: Props) { style.width = '100%'; } + const handleClose = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + props.onClose && props.onClose(); + } + } + return open ? (
{children} @@ -85,4 +93,4 @@ Modal.Header = ModalHeader; Modal.Footer = ModalFooter; Modal.Content = ModalContent; -export default Modal; \ No newline at end of file +export default Modal; From f1920a28bf29a77da9809b2db241adef179c5fca Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 27 Jun 2022 13:34:30 +0200 Subject: [PATCH 05/25] ci(frontend): deploying ee along with oss Signed-off-by: rjshrjndrn --- .github/workflows/frontend.yaml | 61 ++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index dba71939f..2616a0baf 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -51,7 +51,10 @@ jobs: docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "yarn && yarn build" # https://github.com/docker/cli/issues/1134#issuecomment-613516912 DOCKER_BUILDKIT=1 docker build --target=cicd -t $DOCKER_REPO/frontend:${IMAGE_TAG} . + docker tag $DOCKER_REPO/frontend:${IMAGE_TAG} $DOCKER_REPO/frontend:${IMAGE_TAG}-ee docker push $DOCKER_REPO/frontend:${IMAGE_TAG} + docker push $DOCKER_REPO/frontend:${IMAGE_TAG}-ee + - name: Creating old image input run: | # @@ -72,7 +75,7 @@ jobs: EOF done - - name: Deploy to kubernetes + - name: Deploy to kubernetes foss run: | cd scripts/helmcharts/ @@ -94,6 +97,62 @@ jobs: IMAGE_TAG: ${{ github.sha }} ENVIRONMENT: staging + +### Enterprise code deployment + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Creating old image input + env: + IMAGE_TAG: ${{ github.sha }} + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + # We've to strip off the -ee, as helm will append it. + tag: `echo ${image_array[1]} | cut -d '-' -f 1` + EOF + done + + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + + ## Update secerts + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.EE_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.EE_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.EE_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.EE_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.EE_DOMAIN_NAME }}\"/g" vars.yaml + sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/frontend/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # We're not passing -ee flag, because helm will add that. + IMAGE_TAG: ${{ github.sha }} + ENVIRONMENT: staging + # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 From 22b3ffdc6dabafde5f3e409b722ab9009cc68b26 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 27 Jun 2022 13:41:39 +0200 Subject: [PATCH 06/25] ci(worker): cache disabled as it's consuming space, and actions failing Signed-off-by: rjshrjndrn --- .github/workflows/workers-ee.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index e42089602..2be2d06b2 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -35,10 +35,10 @@ jobs: kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. id: setcontext - # Caching docker images - - uses: satackey/action-docker-layer-caching@v0.0.11 - # Ignore the failure of a step and avoid terminating the job. - continue-on-error: true + # # Caching docker images + # - uses: satackey/action-docker-layer-caching@v0.0.11 + # # Ignore the failure of a step and avoid terminating the job. + # continue-on-error: true - name: Build, tag id: build-image From 3f52992e331e9e3e42aca6c494f9faf0ea209ceb Mon Sep 17 00:00:00 2001 From: Alexander Zavorotynskiy Date: Mon, 27 Jun 2022 13:45:11 +0200 Subject: [PATCH 07/25] fix(backend): fixed config var name in integrations service --- backend/internal/config/integrations/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/internal/config/integrations/config.go b/backend/internal/config/integrations/config.go index 290acde1d..86ba7698d 100644 --- a/backend/internal/config/integrations/config.go +++ b/backend/internal/config/integrations/config.go @@ -3,9 +3,9 @@ package integrations import "openreplay/backend/pkg/env" type Config struct { - TopicRawWeb string - PostgresURI string - TokenSecret string + TopicAnalytics string + PostgresURI string + TokenSecret string } func New() *Config { From 5d52d56e12975fceec046dd84a5eb9796d436dfe Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 27 Jun 2022 13:50:22 +0200 Subject: [PATCH 08/25] ci(fix): change kubeconfig auth env Signed-off-by: rjshrjndrn --- .github/workflows/frontend.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 2616a0baf..984365d53 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -36,8 +36,6 @@ jobs: method: kubeconfig kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. id: setcontext -# - name: Install -# run: npm install - name: Building and Pushing frontend image id: build-image @@ -100,11 +98,11 @@ jobs: ### Enterprise code deployment - - uses: azure/k8s-set-context@v1 + - uses: actions-hub/kubectl@master + env: + KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} with: - method: kubeconfig - kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. - id: setcontext + args: get pods - name: Creating old image input env: From 917ab967235ab96c37202cc1c67eacd9cf576ff4 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 27 Jun 2022 14:00:23 +0200 Subject: [PATCH 09/25] ci(fix): kubeconfig path Signed-off-by: rjshrjndrn --- .github/workflows/frontend.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 984365d53..8a70d45e6 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -44,6 +44,7 @@ jobs: IMAGE_TAG: ${{ github.sha }} ENVIRONMENT: staging run: | + set -x cd frontend mv .env.sample .env docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "yarn && yarn build" @@ -55,6 +56,7 @@ jobs: - name: Creating old image input run: | + set -x # # Create yaml with existing image tags # @@ -100,7 +102,7 @@ jobs: - uses: actions-hub/kubectl@master env: - KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} + KUBE_CONFIG: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret.${{ secrets.KUBE_CONFIG }} with: args: get pods From 7787f19c005981f0cf34d40fe4259fc5720969c1 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 27 Jun 2022 11:50:26 +0200 Subject: [PATCH 10/25] change(ui) - version changes in env.sample --- frontend/.env.sample | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/.env.sample b/frontend/.env.sample index 85d169876..acfc72ad6 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -12,7 +12,7 @@ SENTRY_URL = '' # CAPTCHA CAPTCHA_ENABLED = false -CAPTCHA_SITE_KEY = 'asdad' +CAPTCHA_SITE_KEY = '' # MINIO MINIO_ENDPOINT = '' @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = '1.6.0' -TRACKER_VERSION = '3.5.10' +VERSION = '1.7.0' +TRACKER_VERSION = '3.5.12' From 941f8c9b11c73f32580205c3d469823ea8051acf Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 27 Jun 2022 12:00:03 +0200 Subject: [PATCH 11/25] change(ui) - removed unused from header component --- .../app/components/Client/Roles/Roles.tsx | 3 +- .../Roles/components/RoleItem/RoleItem.tsx | 8 ++++-- frontend/app/components/Header/Header.js | 28 ++----------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/frontend/app/components/Client/Roles/Roles.tsx b/frontend/app/components/Client/Roles/Roles.tsx index 9d9a27cec..f9b9ef072 100644 --- a/frontend/app/components/Client/Roles/Roles.tsx +++ b/frontend/app/components/Client/Roles/Roles.tsx @@ -109,10 +109,11 @@ function Roles(props: Props) { icon >
-
+
Title
Project Access
Feature Access
+
{roles.map(role => (
- {role.permissions.map((permission: any) => ( - - ))} +
+ {role.permissions.map((permission: any) => ( + + ))} +
{isAdmin && !!editHandler && diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 293799976..fc1aab477 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -6,12 +6,9 @@ import { sessions, assist, client, - errors, - // funnels, dashboard, withSiteId, CLIENT_DEFAULT_TAB, - isRoute, } from 'App/routes'; import { logout } from 'Duck/user'; import { Icon, Popup } from 'UI'; @@ -20,7 +17,7 @@ import styles from './header.module.css'; import OnboardingExplore from './OnboardingExplore/OnboardingExplore' import Announcements from '../Announcements'; import Notifications from '../Alerts/Notifications'; -import { init as initSite, fetchList as fetchSiteList } from 'Duck/site'; +import { init as initSite } from 'Duck/site'; import ErrorGenPanel from 'App/dev/components'; import Alerts from '../Alerts/Alerts'; @@ -32,18 +29,13 @@ import { useObserver } from 'mobx-react-lite'; const DASHBOARD_PATH = dashboard(); const SESSIONS_PATH = sessions(); const ASSIST_PATH = assist(); -const ERRORS_PATH = errors(); -// const FUNNELS_PATH = funnels(); const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); -const AUTOREFRESH_INTERVAL = 30 * 1000; - -let interval = null; const Header = (props) => { const { sites, location, account, onLogoutClick, siteId, - boardingCompletion = 100, fetchSiteList, showAlerts = false, + boardingCompletion = 100, showAlerts = false, } = props; const name = account.get('name').split(" ")[0]; @@ -98,20 +90,6 @@ const Header = (props) => { > { 'Assist' } - {/* - { 'Errors' } - - - { 'Funnels' } - */} Date: Mon, 27 Jun 2022 12:20:43 +0200 Subject: [PATCH 12/25] change(ui) - error and sessions border --- .../CustomMetricTableErrors/CustomMetricTableErrors.tsx | 4 +++- .../CustomMetricTableSessions.tsx | 4 ++-- .../components/Errors/ErrorListItem/ErrorListItem.tsx | 2 +- .../app/components/Funnels/FunnelWidget/FunnelWidget.tsx | 2 +- frontend/app/duck/roles.js | 8 ++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index b8e982b62..983c590f6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -44,7 +44,9 @@ function CustomMetricTableErrors(props: RouteComponentProps) { >
{metric.data.errors && metric.data.errors.map((error: any, index: any) => ( - onErrorClick(e, error)} /> +
+ onErrorClick(e, error)} /> +
))} {isEdit && ( diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index 4c06664c7..c5ab0ee85 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -20,8 +20,8 @@ function CustomMetricTableSessions(props: Props) { >
{metric.data.sessions && metric.data.sessions.map((session: any, index: any) => ( -
- +
+
))} diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index d6e52638f..2d76241b3 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -27,7 +27,7 @@ function ErrorListItem(props: Props) { // } return (
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 2f7f06bcf..19587be83 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -89,7 +89,7 @@ function EmptyStage({ total }: any) {
{`+${total} ${total > 1 ? 'steps' : 'step'}`}
-
+
)) } diff --git a/frontend/app/duck/roles.js b/frontend/app/duck/roles.js index 8a8415475..139e3c29e 100644 --- a/frontend/app/duck/roles.js +++ b/frontend/app/duck/roles.js @@ -2,7 +2,7 @@ import { List, Map } from 'immutable'; import Role from 'Types/role'; import crudDuckGenerator from './tools/crudDuck'; import { reduceDucks } from 'Duck/tools'; -import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; +import { createListUpdater } from './funcTools/tools'; const crudDuck = crudDuckGenerator('client/role', Role, { idKey: 'roleId' }); export const { fetchList, init, edit, remove, } = crudDuck.actions; @@ -14,14 +14,14 @@ const initialState = Map({ permissions: List([ { text: 'Session Replay', value: 'SESSION_REPLAY' }, { text: 'Developer Tools', value: 'DEV_TOOLS' }, - { text: 'Errors', value: 'ERRORS' }, - { text: 'Metrics', value: 'METRICS' }, + // { text: 'Errors', value: 'ERRORS' }, + { text: 'Dashboard', value: 'METRICS' }, { text: 'Assist (Live)', value: 'ASSIST_LIVE' }, { text: 'Assist (Call)', value: 'ASSIST_CALL' }, ]) }); -const name = "role"; +// const name = "role"; const idKey = "roleId"; const updateItemInList = createListUpdater(idKey); From 16503a2fae5deb98dbf3e0e6cacad926b6349900 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 27 Jun 2022 14:08:12 +0200 Subject: [PATCH 13/25] change(ui) - metric type icon in metric list --- .../Client/ManageUsers/ManageUsers.js | 2 +- .../CustomMetricTableErrors.tsx | 11 +++-- .../MetricListItem/MetricListItem.tsx | 40 +++++++++++++++---- .../components/WidgetChart/WidgetChart.tsx | 2 +- .../Funnels/FunnelWidget/FunnelWidget.tsx | 1 - 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js index 010b8cdea..dd703813e 100644 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ b/frontend/app/components/Client/ManageUsers/ManageUsers.js @@ -24,7 +24,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; errors: state.getIn([ 'members', 'saveRequest', 'errors' ]), loading: state.getIn([ 'members', 'loading' ]), saving: state.getIn([ 'members', 'saveRequest', 'loading' ]), - roles: state.getIn(['roles', 'list']).filter(r => !r.protected).map(r => ({ text: r.name, value: r.roleId })).toJS(), + roles: state.getIn(['roles', 'list']).filter(r => !r.protected).map(r => ({ label: r.name, value: r.roleId })).toJS(), isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', }), { init, diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index 983c590f6..8885edcf6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -1,15 +1,13 @@ import React, { useEffect } from 'react'; import { Pagination, NoContent } from 'UI'; -import ErrorListItem from '../../../components/Errors/ErrorListItem'; +import ErrorListItem from 'App/components/Dashboard/components/Errors/ErrorListItem'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; -import ErrorDetailsModal from '../../../components/Errors/ErrorDetailsModal'; +import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; -const PER_PAGE = 5; interface Props { - metric: any; - isTemplate?: boolean; - isEdit?: boolean; + metric: any, + isEdit: any, history: any, location: any, } @@ -39,6 +37,7 @@ function CustomMetricTableErrors(props: RouteComponentProps) { return ( diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 50bb99164..492a41bd5 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, NoContent, Label, Link, Pagination } from 'UI'; +import { Icon, NoContent, Label, Link, Pagination, Popup } from 'UI'; import { checkForRecent, formatDateTimeDefault, convertTimestampToUtcTimestamp } from 'App/date'; import { getIcon } from 'react-toastify/dist/components'; @@ -24,11 +24,22 @@ function DashboardLink({ dashboards}: any) { ); } -function MetricListItem(props: Props) { - const { metric } = props; +function MetricTypeIcon({ type }: any) { + const PopupWrapper = (props: any) => { + return ( + {type}
} + position="top center" + on="hover" + hideOnScroll={true} + > + {props.children} + + ); + } - const getIcon = (metricType: string) => { - switch (metricType) { + const getIcon = () => { + switch (type) { case 'funnel': return 'filter'; case 'table': @@ -37,13 +48,28 @@ function MetricListItem(props: Props) { return 'bar-chart-line'; } } + + return ( + +
+ +
+
+ ) +} + +function MetricListItem(props: Props) { + const { metric } = props; + + return (
-
+ {/*
-
+
*/} + {metric.name} diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index e6553dcc8..0cccc7a58 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -146,7 +146,7 @@ function WidgetChart(props: Props) { return ( ) diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 19587be83..b843736df 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -74,7 +74,6 @@ function FunnelWidget(props: Props) { Affected users
{funnel.affectedUsers} - {/* (12%) */}
From a14dfb4a79963b9048038e75a18dcd232d8981cd Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 27 Jun 2022 15:05:16 +0200 Subject: [PATCH 14/25] ci(fix): frontend ee deployment Signed-off-by: rjshrjndrn --- .github/workflows/frontend.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 8a70d45e6..88a7de8a3 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -100,11 +100,11 @@ jobs: ### Enterprise code deployment - - uses: actions-hub/kubectl@master - env: - KUBE_CONFIG: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret.${{ secrets.KUBE_CONFIG }} + - uses: azure/k8s-set-context@v1 with: - args: get pods + method: kubeconfig + kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontextee - name: Creating old image input env: From a3e99a6217456a8f35cc3b3bbc76fad5d995c474 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 27 Jun 2022 15:53:03 +0200 Subject: [PATCH 15/25] fix(ui) - end date fix and other changes --- .../Client/Integrations/Integrations.js | 1074 +++++++++-------- .../SlackChannelList/SlackChannelList.js | 7 +- .../CustomMetricTableSessions.tsx | 4 +- frontend/app/types/app/period.js | 254 ++-- 4 files changed, 728 insertions(+), 611 deletions(-) diff --git a/frontend/app/components/Client/Integrations/Integrations.js b/frontend/app/components/Client/Integrations/Integrations.js index 2f4ed073f..b4a421980 100644 --- a/frontend/app/components/Client/Integrations/Integrations.js +++ b/frontend/app/components/Client/Integrations/Integrations.js @@ -1,36 +1,36 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import withPageTitle from 'HOCs/withPageTitle'; -import { Loader, IconButton, Button, Icon, SlideModal } from 'UI'; -import { fetchList as fetchListSlack } from 'Duck/integrations/slack'; -import { remove as removeIntegrationConfig } from 'Duck/integrations/actions'; -import { fetchList, init } from 'Duck/integrations/actions'; -import cn from 'classnames'; +import React from "react"; +import { connect } from "react-redux"; +import withPageTitle from "HOCs/withPageTitle"; +import { Loader, IconButton, SlideModal } from "UI"; +import { fetchList as fetchListSlack } from "Duck/integrations/slack"; +import { remove as removeIntegrationConfig } from "Duck/integrations/actions"; +import { fetchList, init } from "Duck/integrations/actions"; +import cn from "classnames"; -import IntegrationItem from './IntegrationItem'; -import SentryForm from './SentryForm'; -import GithubForm from './GithubForm'; -import SlackForm from './SlackForm'; -import DatadogForm from './DatadogForm'; -import StackdriverForm from './StackdriverForm'; -import RollbarForm from './RollbarForm'; -import NewrelicForm from './NewrelicForm'; -import BugsnagForm from './BugsnagForm'; -import CloudwatchForm from './CloudwatchForm'; -import ElasticsearchForm from './ElasticsearchForm'; -import SumoLogicForm from './SumoLogicForm'; -import JiraForm from './JiraForm'; -import styles from './integrations.module.css'; -import ReduxDoc from './ReduxDoc'; -import VueDoc from './VueDoc'; -import GraphQLDoc from './GraphQLDoc'; -import NgRxDoc from './NgRxDoc/NgRxDoc'; -import SlackAddForm from './SlackAddForm'; -import FetchDoc from './FetchDoc'; -import MobxDoc from './MobxDoc'; -import ProfilerDoc from './ProfilerDoc'; -import AssistDoc from './AssistDoc'; -import AxiosDoc from './AxiosDoc/AxiosDoc'; +import IntegrationItem from "./IntegrationItem"; +import SentryForm from "./SentryForm"; +import GithubForm from "./GithubForm"; +import SlackForm from "./SlackForm"; +import DatadogForm from "./DatadogForm"; +import StackdriverForm from "./StackdriverForm"; +import RollbarForm from "./RollbarForm"; +import NewrelicForm from "./NewrelicForm"; +import BugsnagForm from "./BugsnagForm"; +import CloudwatchForm from "./CloudwatchForm"; +import ElasticsearchForm from "./ElasticsearchForm"; +import SumoLogicForm from "./SumoLogicForm"; +import JiraForm from "./JiraForm"; +import styles from "./integrations.module.css"; +import ReduxDoc from "./ReduxDoc"; +import VueDoc from "./VueDoc"; +import GraphQLDoc from "./GraphQLDoc"; +import NgRxDoc from "./NgRxDoc/NgRxDoc"; +import SlackAddForm from "./SlackAddForm"; +import FetchDoc from "./FetchDoc"; +import MobxDoc from "./MobxDoc"; +import ProfilerDoc from "./ProfilerDoc"; +import AssistDoc from "./AssistDoc"; +import AxiosDoc from "./AxiosDoc/AxiosDoc"; const NONE = -1; const SENTRY = 0; @@ -56,468 +56,578 @@ const ASSIST = 19; const AXIOS = 20; const TITLE = { - [ SENTRY ]: 'Sentry', - [ SLACK ]: 'Slack', - [ DATADOG ]: 'Datadog', - [ STACKDRIVER ]: 'Stackdriver', - [ ROLLBAR ]: 'Rollbar', - [ NEWRELIC ]: 'New Relic', - [ BUGSNAG ]: 'Bugsnag', - [ CLOUDWATCH ]: 'CloudWatch', - [ ELASTICSEARCH ]: 'Elastic Search', - [ SUMOLOGIC ]: 'Sumo Logic', - [ JIRA ]: 'Jira', - [ GITHUB ]: 'Github', - [ REDUX ] : 'Redux', - [ VUE ] : 'VueX', - [ GRAPHQL ] : 'GraphQL', - [ NGRX ] : 'NgRx', - [ FETCH ] : 'Fetch', - [ MOBX ] : 'MobX', - [ PROFILER ] : 'Profiler', - [ ASSIST ] : 'Assist', - [ AXIOS ] : 'Axios', -} + [SENTRY]: "Sentry", + [SLACK]: "Slack", + [DATADOG]: "Datadog", + [STACKDRIVER]: "Stackdriver", + [ROLLBAR]: "Rollbar", + [NEWRELIC]: "New Relic", + [BUGSNAG]: "Bugsnag", + [CLOUDWATCH]: "CloudWatch", + [ELASTICSEARCH]: "Elastic Search", + [SUMOLOGIC]: "Sumo Logic", + [JIRA]: "Jira", + [GITHUB]: "Github", + [REDUX]: "Redux", + [VUE]: "VueX", + [GRAPHQL]: "GraphQL", + [NGRX]: "NgRx", + [FETCH]: "Fetch", + [MOBX]: "MobX", + [PROFILER]: "Profiler", + [ASSIST]: "Assist", + [AXIOS]: "Axios", +}; -const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST] +const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST]; -const integrations = [ 'sentry', 'datadog', 'stackdriver', 'rollbar', 'newrelic', 'bugsnag', 'cloudwatch', 'elasticsearch', 'sumologic', 'issues' ]; +const integrations = [ + "sentry", + "datadog", + "stackdriver", + "rollbar", + "newrelic", + "bugsnag", + "cloudwatch", + "elasticsearch", + "sumologic", + "issues", +]; -@connect(state => { - const props = {}; - integrations.forEach(name => { - props[ `${ name }Integrated`] = name === 'issues' ? - !!(state.getIn([ name, 'list' ]).first() && state.getIn([ name, 'list' ]).first().token) : - state.getIn([ name, 'list' ]).size > 0; - props.loading = props.loading || state.getIn([ name, 'fetchRequest', 'loading']); - }) - const site = state.getIn([ 'site', 'instance' ]); - return { - ...props, - issues: state.getIn([ 'issues', 'list']).first() || {}, - slackChannelListExists: state.getIn([ 'slack', 'list' ]).size > 0, - tenantId: state.getIn([ 'user', 'account', 'tenantId' ]), - jwt: state.get('jwt'), - projectKey: site ? site.projectKey : '' - }; -}, { - fetchList, - init, - fetchListSlack, - removeIntegrationConfig -}) -@withPageTitle('Integrations - OpenReplay Preferences') -export default class Integrations extends React.PureComponent { - state = { - modalContent: NONE, - showDetailContent: false, - }; - - componentWillMount() { - integrations.forEach(name => - this.props.fetchList(name) - ); - this.props.fetchListSlack(); - } - - onClickIntegrationItem = (e, url) => { - e.preventDefault(); - window.open(url); - } - - closeModal = () => this.setState({ modalContent: NONE, showDetailContent: false }); - - onOauthClick = (source) => { - if (source === GITHUB) { - const githubUrl = `https://auth.openreplay.com/oauth/login?provider=github&back_url=${document.location.href}`; - const options = { - method: 'GET', - credentials: 'include', - headers: new Headers({ - 'Authorization': 'Bearer ' + this.props.jwt.toString() - }) - }; - fetch(githubUrl, options).then((resp) => resp.text().then((txt) => window.open(txt, '_self'))) - } - } - - renderDetailContent() { - switch (this.state.modalContent) { - case SLACK: - return this.setState({ showDetailContent: false }) } />; - } - } - - renderModalContent() { - const { projectKey } = this.props; - - switch (this.state.modalContent) { - case SENTRY: - return ; - case GITHUB: - return ; - case SLACK: - return this.setState({ showDetailContent: true })} - /> - case DATADOG: - return ; - case STACKDRIVER: - return ; - case ROLLBAR: - return ; - case NEWRELIC: - return ; - case BUGSNAG: - return ; - case CLOUDWATCH: - return ; - case ELASTICSEARCH: - return ; - case SUMOLOGIC: - return ; - case JIRA: - return ; - case REDUX: - return - case VUE: - return - case GRAPHQL: - return - case NGRX: - return - case FETCH: - return - case MOBX: - return - case PROFILER: - return - case ASSIST: - return - case AXIOS: - return - default: - return null; +@connect( + (state) => { + const props = {}; + integrations.forEach((name) => { + props[`${name}Integrated`] = + name === "issues" + ? !!( + state.getIn([name, "list"]).first() && + state.getIn([name, "list"]).first().token + ) + : state.getIn([name, "list"]).size > 0; + props.loading = + props.loading || state.getIn([name, "fetchRequest", "loading"]); + }); + const site = state.getIn(["site", "instance"]); + return { + ...props, + issues: state.getIn(["issues", "list"]).first() || {}, + slackChannelListExists: state.getIn(["slack", "list"]).size > 0, + tenantId: state.getIn(["user", "account", "tenantId"]), + jwt: state.get("jwt"), + projectKey: site ? site.projectKey : "", + }; + }, + { + fetchList, + init, + fetchListSlack, + removeIntegrationConfig, } - } +) +@withPageTitle("Integrations - OpenReplay Preferences") +export default class Integrations extends React.PureComponent { + state = { + modalContent: NONE, + showDetailContent: false, + }; - deleteHandler = name => { - this.props.removeIntegrationConfig(name, null).then(function() { - this.props.fetchList(name) - }.bind(this)); - } - - showIntegrationConfig = (type) => { - this.setState({ modalContent: type }); - } + componentWillMount() { + integrations.forEach((name) => this.props.fetchList(name)); + this.props.fetchListSlack(); + } - render() { - const { - loading, - sentryIntegrated, - stackdriverIntegrated, - datadogIntegrated, - rollbarIntegrated, - newrelicIntegrated, - bugsnagIntegrated, - cloudwatchIntegrated, - elasticsearchIntegrated, - sumologicIntegrated, - hideHeader=false, - plugins=false, - jiraIntegrated, - issuesIntegrated, - tenantId, - slackChannelListExists, - issues, - } = this.props; - const { modalContent, showDetailContent } = this.state; - return ( -
- -
{TITLE[ modalContent ]}
- { modalContent === SLACK && ( - this.setState({ showDetailContent: true })} - /> - )} -
- } - isDisplayed={ modalContent !== NONE } - onClose={ this.closeModal } - size={ DOCS.includes(this.state.modalContent) ? 'middle' : 'small' } - content={ this.renderModalContent() } - detailContent={ showDetailContent && this.renderDetailContent() } - /> + onClickIntegrationItem = (e, url) => { + e.preventDefault(); + window.open(url); + }; - {!hideHeader && ( -
-

{ 'Integrations' }

-

Power your workflow with your favourite tools.

-
-
- )} + closeModal = () => + this.setState({ modalContent: NONE, showDetailContent: false }); - {plugins && ( -
-
Use plugins to better debug your application's store, monitor queries and track performance issues.
-
- this.showIntegrationConfig(REDUX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(VUE) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(GRAPHQL) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(NGRX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(MOBX) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(FETCH) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(PROFILER) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(AXIOS) } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(ASSIST) } - // integrated={ sentryIntegrated } - /> -
-
- )} + onOauthClick = (source) => { + if (source === GITHUB) { + const githubUrl = `https://auth.openreplay.com/oauth/login?provider=github&back_url=${document.location.href}`; + const options = { + method: "GET", + credentials: "include", + headers: new Headers({ + Authorization: "Bearer " + this.props.jwt.toString(), + }), + }; + fetch(githubUrl, options).then((resp) => + resp.text().then((txt) => window.open(txt, "_self")) + ); + } + }; - {!plugins && ( - -
-
-
How are you monitoring errors and crash reporting?
-
- { - (!issues.token || issues.provider !== 'github') && - this.showIntegrationConfig(JIRA) } - integrated={ issuesIntegrated } - /> - } - { (!issues.token || issues.provider !== 'jira') && - this.showIntegrationConfig(GITHUB) } - integrated={ issuesIntegrated } - deleteHandler={issuesIntegrated ? () => this.deleteHandler('issues') : null} - /> - } - {/* this.showIntegrationConfig(GITHUB) } - integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(JIRA) } - integrated={ sentryIntegrated } - /> */} - this.showIntegrationConfig(SLACK) } - integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(SENTRY) } - integrated={ sentryIntegrated } - /> + renderDetailContent() { + switch (this.state.modalContent) { + case SLACK: + return ( + + this.setState({ showDetailContent: false }) + } + /> + ); + } + } - this.showIntegrationConfig(BUGSNAG) } - integrated={ bugsnagIntegrated } - /> + renderModalContent() { + const { projectKey } = this.props; - this.showIntegrationConfig(ROLLBAR) } - integrated={ rollbarIntegrated } - /> + switch (this.state.modalContent) { + case SENTRY: + return ; + case GITHUB: + return ; + case SLACK: + return ( + + this.setState({ showDetailContent: true }) + } + /> + ); + case DATADOG: + return ; + case STACKDRIVER: + return ; + case ROLLBAR: + return ; + case NEWRELIC: + return ; + case BUGSNAG: + return ; + case CLOUDWATCH: + return ; + case ELASTICSEARCH: + return ; + case SUMOLOGIC: + return ; + case JIRA: + return ; + case REDUX: + return ( + + ); + case VUE: + return ( + + ); + case GRAPHQL: + return ( + + ); + case NGRX: + return ( + + ); + case FETCH: + return ( + + ); + case MOBX: + return ( + + ); + case PROFILER: + return ( + + ); + case ASSIST: + return ( + + ); + case AXIOS: + return ( + + ); + default: + return null; + } + } - this.showIntegrationConfig(ELASTICSEARCH) } - integrated={ elasticsearchIntegrated } - /> + deleteHandler = (name) => { + this.props.removeIntegrationConfig(name, null).then( + function () { + this.props.fetchList(name); + }.bind(this) + ); + }; - this.showIntegrationConfig(DATADOG) } - integrated={ datadogIntegrated } - /> - this.showIntegrationConfig(SUMOLOGIC) } - integrated={ sumologicIntegrated } - /> - this.showIntegrationConfig(STACKDRIVER) } - integrated={ stackdriverIntegrated } - /> + showIntegrationConfig = (type) => { + this.setState({ modalContent: type }); + }; - this.showIntegrationConfig(CLOUDWATCH) } - integrated={ cloudwatchIntegrated } - /> + render() { + const { + loading, + sentryIntegrated, + stackdriverIntegrated, + datadogIntegrated, + rollbarIntegrated, + newrelicIntegrated, + bugsnagIntegrated, + cloudwatchIntegrated, + elasticsearchIntegrated, + sumologicIntegrated, + hideHeader = false, + plugins = false, + jiraIntegrated, + issuesIntegrated, + tenantId, + slackChannelListExists, + issues, + } = this.props; + const { modalContent, showDetailContent } = this.state; + return ( +
+ +
{TITLE[modalContent]}
+ {modalContent === SLACK && ( + + this.setState({ + showDetailContent: true, + }) + } + /> + )} +
+ } + isDisplayed={modalContent !== NONE} + onClose={this.closeModal} + size={ + DOCS.includes(this.state.modalContent) + ? "middle" + : "small" + } + content={this.renderModalContent()} + detailContent={ + showDetailContent && this.renderDetailContent() + } + /> - this.showIntegrationConfig(NEWRELIC) } - integrated={ newrelicIntegrated } - /> -
-
- - {/*
-
How are you logging backend errors?
-
- -
-
*/} - {/* - - */} -
-
- )} -
- ); - } -} \ No newline at end of file + {!hideHeader && ( +
+

+ {"Integrations"} +

+

+ Power your workflow with your favourite tools. +

+
+
+ )} + + {plugins && ( +
+
+ Use plugins to better debug your application's + store, monitor queries and track performance issues. +
+
+ + this.showIntegrationConfig(REDUX) + } + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(VUE)} + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(GRAPHQL) + } + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(NGRX)} + // integrated={ sentryIntegrated } + /> + this.showIntegrationConfig(MOBX)} + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(FETCH) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(PROFILER) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(AXIOS) + } + // integrated={ sentryIntegrated } + /> + + this.showIntegrationConfig(ASSIST) + } + // integrated={ sentryIntegrated } + /> +
+
+ )} + + {!plugins && ( + +
+
+
+ How are you monitoring errors and crash + reporting? +
+
+ {(!issues.token || + issues.provider !== "github") && ( + + this.showIntegrationConfig(JIRA) + } + integrated={issuesIntegrated} + /> + )} + {(!issues.token || + issues.provider !== "jira") && ( + + this.showIntegrationConfig( + GITHUB + ) + } + integrated={issuesIntegrated} + deleteHandler={ + issuesIntegrated + ? () => + this.deleteHandler( + "issues" + ) + : null + } + /> + )} + + + this.showIntegrationConfig(SLACK) + } + integrated={sentryIntegrated} + /> + + this.showIntegrationConfig(SENTRY) + } + integrated={sentryIntegrated} + /> + + + this.showIntegrationConfig(BUGSNAG) + } + integrated={bugsnagIntegrated} + /> + + + this.showIntegrationConfig(ROLLBAR) + } + integrated={rollbarIntegrated} + /> + + + this.showIntegrationConfig( + ELASTICSEARCH + ) + } + integrated={elasticsearchIntegrated} + /> + + + this.showIntegrationConfig(DATADOG) + } + integrated={datadogIntegrated} + /> + + this.showIntegrationConfig( + SUMOLOGIC + ) + } + integrated={sumologicIntegrated} + /> + + this.showIntegrationConfig( + STACKDRIVER + ) + } + integrated={stackdriverIntegrated} + /> + + + this.showIntegrationConfig( + CLOUDWATCH + ) + } + integrated={cloudwatchIntegrated} + /> + + + this.showIntegrationConfig(NEWRELIC) + } + integrated={newrelicIntegrated} + /> +
+
+
+
+ )} +
+ ); + } +} diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js index e854dfce2..f78527204 100644 --- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js +++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js @@ -16,9 +16,10 @@ function SlackChannelList(props) {
-
Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page.
- +
+
Integrate Slack with OpenReplay and share insights with the rest of the team, directly from the recording page.
+ {/* */} +
} size="small" diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index c5ab0ee85..faf725da6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,8 +1,7 @@ import { useObserver } from 'mobx-react-lite'; -import React, { useEffect } from 'react'; +import React from 'react'; import SessionItem from 'Shared/SessionItem'; import { Pagination, NoContent } from 'UI'; -import { useModal } from 'App/components/Modal'; interface Props { metric: any; @@ -17,6 +16,7 @@ function CustomMetricTableSessions(props: Props) {
{metric.data.sessions && metric.data.sessions.map((session: any, index: any) => ( diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 1922e55b3..646f0087e 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -1,132 +1,138 @@ -import origMoment from 'moment'; -import { extendMoment } from 'moment-range'; -import Record from 'Types/Record'; +import origMoment from "moment"; +import { extendMoment } from "moment-range"; +import Record from "Types/Record"; const moment = extendMoment(origMoment); -export const LAST_30_MINUTES = 'LAST_30_MINUTES'; -export const TODAY = 'TODAY'; -export const LAST_24_HOURS = 'LAST_24_HOURS'; -export const YESTERDAY = 'YESTERDAY'; -export const LAST_7_DAYS = 'LAST_7_DAYS'; -export const LAST_30_DAYS = 'LAST_30_DAYS'; -export const THIS_MONTH = 'THIS_MONTH'; -export const LAST_MONTH = 'LAST_MONTH'; -export const THIS_YEAR = 'THIS_YEAR'; -export const CUSTOM_RANGE = 'CUSTOM_RANGE'; +export const LAST_30_MINUTES = "LAST_30_MINUTES"; +export const TODAY = "TODAY"; +export const LAST_24_HOURS = "LAST_24_HOURS"; +export const YESTERDAY = "YESTERDAY"; +export const LAST_7_DAYS = "LAST_7_DAYS"; +export const LAST_30_DAYS = "LAST_30_DAYS"; +export const THIS_MONTH = "THIS_MONTH"; +export const LAST_MONTH = "LAST_MONTH"; +export const THIS_YEAR = "THIS_YEAR"; +export const CUSTOM_RANGE = "CUSTOM_RANGE"; const RANGE_LABELS = { - [ LAST_30_MINUTES ]: 'Last 30 Minutes', - [ TODAY ]: 'Today', - [ YESTERDAY ]: 'Yesterday', - [ LAST_24_HOURS ]: 'Last 24 Hours', - [ LAST_7_DAYS ]: 'Last 7 Days', - [ LAST_30_DAYS ]: 'Last 30 Days', - [ THIS_MONTH ]: 'This Month', - [ LAST_MONTH ]: 'Last Month', - [ THIS_YEAR ]: 'This Year', -} + [LAST_30_MINUTES]: "Last 30 Minutes", + [TODAY]: "Today", + [YESTERDAY]: "Yesterday", + [LAST_24_HOURS]: "Last 24 Hours", + [LAST_7_DAYS]: "Last 7 Days", + [LAST_30_DAYS]: "Last 30 Days", + [THIS_MONTH]: "This Month", + [LAST_MONTH]: "Last Month", + [THIS_YEAR]: "This Year", +}; function getRange(rangeName) { - switch (rangeName) { - case TODAY: - return moment.range( - moment().startOf('day'), - moment().endOf('day'), - ); - case YESTERDAY: - return moment.range( - moment().subtract(1, 'days').startOf('day'), - moment().subtract(1, 'days').endOf('day'), - ); - case LAST_24_HOURS: - return moment.range( - moment().startOf('hour').subtract(24, 'hours'), - moment().startOf('hour'), - ); - case LAST_30_MINUTES: - return moment.range( - moment().startOf('hour').subtract(30, 'minutes'), - moment().startOf('hour'), - ); - case LAST_7_DAYS: - return moment.range( - moment().subtract(7, 'days').startOf('day'), - moment().endOf('day'), - ); - case LAST_30_DAYS: - return moment.range( - moment().subtract(30, 'days').startOf('day'), - moment().endOf('day'), - ); - case THIS_MONTH: - return moment().range('month'); - case LAST_MONTH: - return moment().subtract(1, 'months').range('month'); - case THIS_YEAR: - return moment().range('year'); - default: - return moment.range(); - } + switch (rangeName) { + case TODAY: + return moment.range(moment().startOf("day"), moment().endOf("day")); + case YESTERDAY: + return moment.range( + moment().subtract(1, "days").startOf("day"), + moment().subtract(1, "days").endOf("day") + ); + case LAST_24_HOURS: + return moment.range( + // moment().startOf("hour").subtract(24, "hours"), + // moment().startOf("hour") + moment().subtract(24, 'hours'), + moment(), + ); + case LAST_30_MINUTES: + return moment.range( + moment().startOf("hour").subtract(30, "minutes"), + moment().startOf("hour") + ); + case LAST_7_DAYS: + return moment.range( + moment().subtract(7, "days").startOf("day"), + moment().endOf("day") + ); + case LAST_30_DAYS: + return moment.range( + moment().subtract(30, "days").startOf("day"), + moment().endOf("day") + ); + case THIS_MONTH: + return moment().range("month"); + case LAST_MONTH: + return moment().subtract(1, "months").range("month"); + case THIS_YEAR: + return moment().range("year"); + default: + return moment.range(); + } } -export default Record({ - start: 0, - end: 0, - rangeName: CUSTOM_RANGE, - range: moment.range(), -}, { - fromJS: period => { - if (!period.rangeName || period.rangeName === CUSTOM_RANGE) { - const range = moment.range( - moment(period.start || 0), - moment(period.end || 0), - ); - return { - ...period, - range, - start: range.start.unix() * 1000, - end: range.end.unix() * 1000, - }; - } - const range = getRange(period.rangeName); - return { - ...period, - range, - start: range.start.unix() * 1000, - end: range.end.unix() * 1000, - } - }, - // fromFilter: filter => { - // const range = getRange(filter.rangeName); - // return { - // start: range.start.unix() * 1000, - // end: range.end.unix() * 1000, - // rangeName: filter.rangeName, - // } - // }, - methods: { - toJSON() { - return { - startDate: this.start, - endDate: this.end, - rangeName: this.rangeName, - rangeValue: this.rangeName, - } - }, - toTimestamps() { - return { - startTimestamp: this.start, - endTimestamp: this.end, - }; - }, - rangeFormatted(format = 'MMM Do YY, hh:mm A') { - return this.range.start.format(format) + ' - ' + this.range.end.format(format); - }, - toTimestampstwo() { - return { - startTimestamp: this.start / 1000, - endTimestamp: this.end / 1000, - }; - }, - } -}); \ No newline at end of file +export default Record( + { + start: 0, + end: 0, + rangeName: CUSTOM_RANGE, + range: moment.range(), + }, + { + fromJS: (period) => { + if (!period.rangeName || period.rangeName === CUSTOM_RANGE) { + const range = moment.range( + moment(period.start || 0), + moment(period.end || 0) + ); + return { + ...period, + range, + start: range.start.unix() * 1000, + end: range.end.unix() * 1000, + }; + } + const range = getRange(period.rangeName); + return { + ...period, + range, + start: range.start.unix() * 1000, + end: range.end.unix() * 1000, + }; + }, + // fromFilter: filter => { + // const range = getRange(filter.rangeName); + // return { + // start: range.start.unix() * 1000, + // end: range.end.unix() * 1000, + // rangeName: filter.rangeName, + // } + // }, + methods: { + toJSON() { + return { + startDate: this.start, + endDate: this.end, + rangeName: this.rangeName, + rangeValue: this.rangeName, + }; + }, + toTimestamps() { + return { + startTimestamp: this.start, + endTimestamp: this.end, + }; + }, + rangeFormatted(format = "MMM Do YY, hh:mm A") { + return ( + this.range.start.format(format) + + " - " + + this.range.end.format(format) + ); + }, + toTimestampstwo() { + return { + startTimestamp: this.start / 1000, + endTimestamp: this.end / 1000, + }; + }, + }, + } +); From db6609d9083cd8f146969ac8aec87eca6234abb0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 27 Jun 2022 16:32:03 +0200 Subject: [PATCH 16/25] fix(ui) - duration and not data message for sessions and errors --- frontend/.prettierrc | 4 + .../CustomMetricTableErrors.tsx | 91 ++- .../CustomMetricTableSessions.tsx | 62 +- .../DashboardView/DashboardView.tsx | 182 +++-- .../WidgetSessions/WidgetSessions.tsx | 150 ++-- frontend/app/dateRange.js | 152 ++-- frontend/app/mstore/dashboardStore.ts | 677 ++++++++++-------- frontend/app/types/app/period.js | 2 +- 8 files changed, 775 insertions(+), 545 deletions(-) create mode 100644 frontend/.prettierrc diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 000000000..5a938ce18 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index 8885edcf6..9e42f9ea6 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -1,59 +1,75 @@ -import React, { useEffect } from 'react'; -import { Pagination, NoContent } from 'UI'; -import ErrorListItem from 'App/components/Dashboard/components/Errors/ErrorListItem'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useModal } from 'App/components/Modal'; -import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; - +import React, { useEffect } from "react"; +import { Pagination, NoContent } from "UI"; +import ErrorListItem from "App/components/Dashboard/components/Errors/ErrorListItem"; +import { withRouter, RouteComponentProps } from "react-router-dom"; +import { useModal } from "App/components/Modal"; +import ErrorDetailsModal from "App/components/Dashboard/components/Errors/ErrorDetailsModal"; +import { useStore } from "App/mstore"; +import { overPastString } from "App/dateRange"; interface Props { - metric: any, - isEdit: any, - history: any, - location: any, + metric: any; + isEdit: any; + history: any; + location: any; } function CustomMetricTableErrors(props: RouteComponentProps) { const { metric, isEdit = false } = props; const errorId = new URLSearchParams(props.location.search).get("errorId"); const { showModal, hideModal } = useModal(); + const { dashboardStore } = useStore(); + const period = dashboardStore.period; const onErrorClick = (e: any, error: any) => { e.stopPropagation(); - props.history.replace({search: (new URLSearchParams({errorId : error.errorId})).toString()}); - } + props.history.replace({ + search: new URLSearchParams({ errorId: error.errorId }).toString(), + }); + }; useEffect(() => { if (!errorId) return; - showModal(, { right: true, onClose: () => { - if (props.history.location.pathname.includes("/dashboard")) { - props.history.replace({search: ""}); - } - }}); + showModal(, { + right: true, + onClose: () => { + if (props.history.location.pathname.includes("/dashboard")) { + props.history.replace({ search: "" }); + } + }, + }); return () => { hideModal(); - } - }, [errorId]) + }; + }, [errorId]); return (
- {metric.data.errors && metric.data.errors.map((error: any, index: any) => ( -
- onErrorClick(e, error)} /> -
- ))} + {metric.data.errors && + metric.data.errors.map((error: any, index: any) => ( +
+ onErrorClick(e, error)} + /> +
+ ))} {isEdit && (
metric.updateKey('page', page)} + totalPages={Math.ceil( + metric.data.total / metric.limit + )} + onPageChange={(page: any) => + metric.updateKey("page", page) + } limit={metric.limit} debounceRequest={500} /> @@ -68,14 +84,17 @@ function CustomMetricTableErrors(props: RouteComponentProps) { ); } -export default withRouter(CustomMetricTableErrors) as React.FunctionComponent>; +export default withRouter(CustomMetricTableErrors) as React.FunctionComponent< + RouteComponentProps +>; -const ViewMore = ({ total, limit }: any) => total > limit && ( -
-
-
- All {total} errors +const ViewMore = ({ total, limit }: any) => + total > limit && ( +
+
+
+ All {total} errors +
-
-); \ No newline at end of file + ); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index faf725da6..4ca953dcc 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,7 +1,9 @@ -import { useObserver } from 'mobx-react-lite'; -import React from 'react'; -import SessionItem from 'Shared/SessionItem'; -import { Pagination, NoContent } from 'UI'; +import { useObserver } from "mobx-react-lite"; +import React from "react"; +import SessionItem from "Shared/SessionItem"; +import { Pagination, NoContent } from "UI"; +import { useStore } from "App/mstore"; +import { overPastString } from "App/dateRange"; interface Props { metric: any; @@ -11,26 +13,41 @@ interface Props { function CustomMetricTableSessions(props: Props) { const { isEdit = false, metric } = props; - + const { dashboardStore } = useStore(); + const period = dashboardStore.period; + return useObserver(() => (
- {metric.data.sessions && metric.data.sessions.map((session: any, index: any) => ( -
- -
- ))} - + {metric.data.sessions && + metric.data.sessions.map((session: any, index: any) => ( +
+ +
+ ))} + {isEdit && (
metric.updateKey('page', page)} + totalPages={Math.ceil( + metric.data.total / metric.limit + )} + onPageChange={(page: any) => + metric.updateKey("page", page) + } limit={metric.data.total} debounceRequest={500} /> @@ -47,12 +64,13 @@ function CustomMetricTableSessions(props: Props) { export default CustomMetricTableSessions; -const ViewMore = ({ total, limit }: any) => total > limit && ( -
-
-
- All {total} sessions +const ViewMore = ({ total, limit }: any) => + total > limit && ( +
+
+
+ All {total} sessions +
-
-); \ No newline at end of file + ); diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 756b7e240..88f859c42 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,27 +1,27 @@ -import React, { useEffect } from 'react'; -import { observer } from 'mobx-react-lite'; -import { useStore } from 'App/mstore'; -import { Button, PageTitle, Loader, NoContent } from 'UI'; -import { withSiteId } from 'App/routes'; -import withModal from 'App/components/Modal/withModal'; -import DashboardWidgetGrid from '../DashboardWidgetGrid'; -import { confirm } from 'UI'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useModal } from 'App/components/Modal'; -import DashboardModal from '../DashboardModal'; -import DashboardEditModal from '../DashboardEditModal'; -import AlertFormModal from 'App/components/Alerts/AlertFormModal'; -import withPageTitle from 'HOCs/withPageTitle'; -import withReport from 'App/components/hocs/withReport'; -import DashboardOptions from '../DashboardOptions'; -import SelectDateRange from 'Shared/SelectDateRange'; -import DashboardIcon from '../../../../svg/dashboard-icn.svg'; -import { Tooltip } from 'react-tippy'; +import React, { useEffect } from "react"; +import { observer } from "mobx-react-lite"; +import { useStore } from "App/mstore"; +import { Button, PageTitle, Loader, NoContent } from "UI"; +import { withSiteId } from "App/routes"; +import withModal from "App/components/Modal/withModal"; +import DashboardWidgetGrid from "../DashboardWidgetGrid"; +import { confirm } from "UI"; +import { withRouter, RouteComponentProps } from "react-router-dom"; +import { useModal } from "App/components/Modal"; +import DashboardModal from "../DashboardModal"; +import DashboardEditModal from "../DashboardEditModal"; +import AlertFormModal from "App/components/Alerts/AlertFormModal"; +import withPageTitle from "HOCs/withPageTitle"; +import withReport from "App/components/hocs/withReport"; +import DashboardOptions from "../DashboardOptions"; +import SelectDateRange from "Shared/SelectDateRange"; +import DashboardIcon from "../../../../svg/dashboard-icn.svg"; +import { Tooltip } from "react-tippy"; interface IProps { siteId: string; - dashboardId: any - renderReport?: any + dashboardId: any; + renderReport?: any; } type Props = IProps & RouteComponentProps; @@ -39,76 +39,108 @@ function DashboardView(props: Props) { const dashboard: any = dashboardStore.selectedDashboard; const period = dashboardStore.period; - const queryParams = new URLSearchParams(props.location.search) + const queryParams = new URLSearchParams(props.location.search); useEffect(() => { if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId) + dashboardStore.fetch(dashboard.dashboardId); }, [dashboard]); const trimQuery = () => { - if (!queryParams.has('modal')) return; - queryParams.delete('modal') + if (!queryParams.has("modal")) return; + queryParams.delete("modal"); props.history.replace({ search: queryParams.toString(), - }) - } + }); + }; const pushQuery = () => { - if (!queryParams.has('modal')) props.history.push('?modal=addMetric') - } + if (!queryParams.has("modal")) props.history.push("?modal=addMetric"); + }; useEffect(() => { if (!dashboardId) dashboardStore.selectDefaultDashboard(); - if (queryParams.has('modal')) { + if (queryParams.has("modal")) { onAddWidgets(); trimQuery(); } }, []); const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard) - showModal(, { right: true }) - } + dashboardStore.initDashboard(dashboard); + showModal( + , + { right: true } + ); + }; const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard) + dashboardStore.initDashboard(dashboard); setFocusedInput(isTitle); - setShowEditModal(true) - } + setShowEditModal(true); + }; const onDelete = async () => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?` - })) { + if ( + await confirm({ + header: "Confirm", + confirmButton: "Yes, delete", + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { dashboardStore.deleteDashboard(dashboard).then(() => { - dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { - props.history.push(withSiteId(`/dashboard/${dashboardId}`, siteId)); - }, () => { - props.history.push(withSiteId('/dashboard', siteId)); - }) + dashboardStore.selectDefaultDashboard().then( + ({ dashboardId }) => { + props.history.push( + withSiteId(`/dashboard/${dashboardId}`, siteId) + ); + }, + () => { + props.history.push(withSiteId("/dashboard", siteId)); + } + ); }); } - } + }; return ( - - Gather and analyze
important metrics in one place.
+ + + Gather and analyze
important metrics in one + place. +
} size="small" subtext={ - + } > -
+
setShowEditModal(false)} @@ -118,23 +150,41 @@ function DashboardView(props: Props) {
{dashboard?.name}} + title={ + + {dashboard?.name} + + } onDoubleClick={() => onEdit(true)} className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" actionButton={ - + } /> -
-
-
+
+
dashboardStore.setPeriod(period)} + onChange={(period: any) => + dashboardStore.setPeriod(period) + } right={true} />
@@ -150,7 +200,9 @@ function DashboardView(props: Props) {
-

{dashboard?.description}

+

+ {dashboard?.description} +

dashboardStore.updateKey('showAlertModal', false)} + onClose={() => + dashboardStore.updateKey("showAlertModal", false) + } />
@@ -168,6 +222,6 @@ function DashboardView(props: Props) { ); } -export default withPageTitle('Dashboards - OpenReplay')( +export default withPageTitle("Dashboards - OpenReplay")( withReport(withRouter(withModal(observer(DashboardView)))) ); diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index d93893008..66a4654e3 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -1,21 +1,21 @@ -import React, { useEffect, useState } from 'react'; -import { NoContent, Loader, Pagination } from 'UI'; -import Select from 'Shared/Select'; -import cn from 'classnames'; -import { useStore } from 'App/mstore'; -import SessionItem from 'Shared/SessionItem'; -import { observer, useObserver } from 'mobx-react-lite'; -import { DateTime } from 'luxon'; -import { debounce } from 'App/utils'; -import useIsMounted from 'App/hooks/useIsMounted' -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import React, { useEffect, useState } from "react"; +import { NoContent, Loader, Pagination } from "UI"; +import Select from "Shared/Select"; +import cn from "classnames"; +import { useStore } from "App/mstore"; +import SessionItem from "Shared/SessionItem"; +import { observer, useObserver } from "mobx-react-lite"; +import { DateTime } from "luxon"; +import { debounce } from "App/utils"; +import useIsMounted from "App/hooks/useIsMounted"; +import AnimatedSVG, { ICONS } from "Shared/AnimatedSVG/AnimatedSVG"; interface Props { className?: string; } function WidgetSessions(props: Props) { - const { className = '' } = props; - const [activeSeries, setActiveSeries] = useState('all'); + const { className = "" } = props; + const [activeSeries, setActiveSeries] = useState("all"); const [data, setData] = useState([]); const isMounted = useIsMounted(); const [loading, setLoading] = useState(false); @@ -23,15 +23,14 @@ function WidgetSessions(props: Props) { const { dashboardStore, metricStore } = useStore(); const filter = useObserver(() => dashboardStore.drillDownFilter); const widget: any = useObserver(() => metricStore.instance); - // const drillDownPeriod = useObserver(() => dashboardStore.drillDownPeriod); - const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm'); - const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm'); - // const [timestamps, setTimestamps] = useState({ - // startTimestamp: 0, - // endTimestamp: 0, - // }); + const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat( + "LLL dd, yyyy HH:mm" + ); + const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat( + "LLL dd, yyyy HH:mm" + ); const [seriesOptions, setSeriesOptions] = useState([ - { label: 'All', value: 'all' }, + { label: "All", value: "all" }, ]); const writeOption = ({ value }: any) => setActiveSeries(value.value); @@ -41,49 +40,68 @@ function WidgetSessions(props: Props) { label: item.seriesName, value: item.seriesId, })); - setSeriesOptions([ - { label: 'All', value: 'all' }, - ...seriesOptions, - ]); + setSeriesOptions([{ label: "All", value: "all" }, ...seriesOptions]); }, [data]); const fetchSessions = (metricId: any, filter: any) => { if (!isMounted()) return; - setLoading(true) - widget.fetchSessions(metricId, filter).then((res: any) => { - setData(res) - }).finally(() => { - setLoading(false) - }); - } - const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []); + setLoading(true); + widget + .fetchSessions(metricId, filter) + .then((res: any) => { + setData(res); + }) + .finally(() => { + setLoading(false); + }); + }; + const debounceRequest: any = React.useCallback( + debounce(fetchSessions, 1000), + [] + ); const depsString = JSON.stringify(widget.series); useEffect(() => { - debounceRequest(widget.metricId, { ...filter, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); - }, [filter.startTimestamp, filter.endTimestamp, filter.filters, depsString, metricStore.sessionsPage]); - - // useEffect(() => { - // const timestamps = drillDownPeriod.toTimestamps(); - // // console.log('timestamps', timestamps); - // debounceRequest(widget.metricId, { startTime: timestamps.startTimestamp, endTime: timestamps.endTimestamp, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); - // }, [drillDownPeriod]); + debounceRequest(widget.metricId, { + ...filter, + series: widget.toJsonDrilldown(), + page: metricStore.sessionsPage, + limit: metricStore.sessionsPageSize, + }); + }, [ + filter.startTimestamp, + filter.endTimestamp, + filter.filters, + depsString, + metricStore.sessionsPage, + ]); return useObserver(() => (

Sessions

-
between {startTime} and {endTime}
+
+ between{" "} + + {startTime} + {" "} + and{" "} + + {endTime} + {" "} +
- { widget.metricType !== 'table' && ( + {widget.metricType !== "table" && (
- Filter by Series + + Filter by Series +