From 2c85c6c05b35d9ad99085c8df475147224fc1ac9 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 23 Dec 2022 12:09:15 +0100 Subject: [PATCH 01/25] fix(frontend): load unprocessed like in saas --- frontend/app/player/web/network/loadFiles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/player/web/network/loadFiles.ts b/frontend/app/player/web/network/loadFiles.ts index ac644eede..ec174fc0e 100644 --- a/frontend/app/player/web/network/loadFiles.ts +++ b/frontend/app/player/web/network/loadFiles.ts @@ -39,11 +39,11 @@ export const loadFiles = ( export async function requestEFSDom(sessionId: string) { - return await requestEFSMobFile(sessionId) + return await requestEFSMobFile(sessionId + "/dom.mob") } export async function requestEFSDevtools(sessionId: string) { - return await requestEFSMobFile(sessionId + "devtools") + return await requestEFSMobFile(sessionId + "/devtools.mob") } async function requestEFSMobFile(filename: string) { From 205fdcba424ac89173bd05b6faec5cabce7d8d7e Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 24 Dec 2022 20:53:11 +0100 Subject: [PATCH 02/25] fix(backend/clickhouse): fixed batch insert panic because of wrong error source (#900) --- ee/backend/pkg/db/clickhouse/connector.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index de94fedc2..20996c073 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -289,7 +289,13 @@ func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *types.E keys = append(keys, k) values = append(values, v) } - + // Check error source before insert to avoid panic from clickhouse lib + switch msg.Source { + case "js_exception", "bugsnag", "cloudwatch", "datadog", "elasticsearch", "newrelic", "rollbar", "sentry", "stackdriver", "sumologic": + default: + return fmt.Errorf("unknown error source: %s", msg.Source) + } + // Insert event to batch if err := c.batches["errors"].Append( session.SessionID, uint16(session.ProjectID), From f1d852abb4392901db8159768dfb319ff83ed6cc Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Dec 2022 15:59:32 +0100 Subject: [PATCH 03/25] [DB] moved click events to bulks (#892) * feat(backend): moved click events to bulks * feat(backend): insert click event with host + path --- backend/pkg/db/postgres/connector.go | 72 ++++++++++++++++-------- backend/pkg/db/postgres/messages-web.go | 41 +++----------- backend/pkg/handlers/web/networkIssue.go | 14 ----- backend/pkg/messages/cache.go | 22 ++++++++ backend/pkg/messages/iterator.go | 17 +++++- 5 files changed, 93 insertions(+), 73 deletions(-) create mode 100644 backend/pkg/messages/cache.go diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index 07314ae68..51773c1e2 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -41,6 +41,8 @@ type Conn struct { webIssues Bulk webIssueEvents Bulk webCustomEvents Bulk + webClickEvents Bulk + webNetworkRequest Bulk sessionUpdates map[uint64]*sessionUpdates batchQueueLimit int batchSizeLimit int @@ -111,25 +113,25 @@ func (conn *Conn) initBulks() { "autocomplete", "(value, type, project_id)", "($%d, $%d, $%d)", - 3, 100) + 3, 200) if err != nil { - log.Fatalf("can't create autocomplete bulk") + log.Fatalf("can't create autocomplete bulk: %s", err) } conn.requests, err = NewBulk(conn.c, "events_common.requests", "(session_id, timestamp, seq_index, url, duration, success)", "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", - 6, 100) + 6, 200) if err != nil { - log.Fatalf("can't create requests bulk") + log.Fatalf("can't create requests bulk: %s", err) } conn.customEvents, err = NewBulk(conn.c, "events_common.customs", "(session_id, timestamp, seq_index, name, payload)", "($%d, $%d, $%d, left($%d, 2700), $%d)", - 5, 100) + 5, 200) if err != nil { - log.Fatalf("can't create customEvents bulk") + log.Fatalf("can't create customEvents bulk: %s", err) } conn.webPageEvents, err = NewBulk(conn.c, "events.pages", @@ -138,73 +140,89 @@ func (conn *Conn) initBulks() { "time_to_interactive, response_time, dom_building_time)", "($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0),"+ " NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0))", - 18, 100) + 18, 200) if err != nil { - log.Fatalf("can't create webPageEvents bulk") + log.Fatalf("can't create webPageEvents bulk: %s", err) } conn.webInputEvents, err = NewBulk(conn.c, "events.inputs", "(session_id, message_id, timestamp, value, label)", "($%d, $%d, $%d, $%d, NULLIF($%d,''))", - 5, 100) + 5, 200) if err != nil { - log.Fatalf("can't create webPageEvents bulk") + log.Fatalf("can't create webPageEvents bulk: %s", err) } conn.webGraphQL, err = NewBulk(conn.c, "events.graphql", "(session_id, timestamp, message_id, name, request_body, response_body)", "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", - 6, 100) + 6, 200) if err != nil { - log.Fatalf("can't create webPageEvents bulk") + log.Fatalf("can't create webPageEvents bulk: %s", err) } conn.webErrors, err = NewBulk(conn.c, "errors", "(error_id, project_id, source, name, message, payload)", "($%d, $%d, $%d, $%d, $%d, $%d::jsonb)", - 6, 100) + 6, 200) if err != nil { - log.Fatalf("can't create webErrors bulk") + log.Fatalf("can't create webErrors bulk: %s", err) } conn.webErrorEvents, err = NewBulk(conn.c, "events.errors", "(session_id, message_id, timestamp, error_id)", "($%d, $%d, $%d, $%d)", - 4, 100) + 4, 200) if err != nil { - log.Fatalf("can't create webErrorEvents bulk") + log.Fatalf("can't create webErrorEvents bulk: %s", err) } conn.webErrorTags, err = NewBulk(conn.c, "public.errors_tags", "(session_id, message_id, error_id, key, value)", "($%d, $%d, $%d, $%d, $%d)", - 5, 100) + 5, 200) if err != nil { - log.Fatalf("can't create webErrorEvents bulk") + log.Fatalf("can't create webErrorEvents bulk: %s", err) } conn.webIssues, err = NewBulk(conn.c, "issues", "(project_id, issue_id, type, context_string)", "($%d, $%d, $%d, $%d)", - 4, 100) + 4, 200) if err != nil { - log.Fatalf("can't create webIssues bulk") + log.Fatalf("can't create webIssues bulk: %s", err) } conn.webIssueEvents, err = NewBulk(conn.c, "events_common.issues", "(session_id, issue_id, timestamp, seq_index, payload)", "($%d, $%d, $%d, $%d, CAST($%d AS jsonb))", - 5, 100) + 5, 200) if err != nil { - log.Fatalf("can't create webIssueEvents bulk") + log.Fatalf("can't create webIssueEvents bulk: %s", err) } conn.webCustomEvents, err = NewBulk(conn.c, "events_common.customs", "(session_id, seq_index, timestamp, name, payload, level)", "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", - 6, 100) + 6, 200) if err != nil { - log.Fatalf("can't create webCustomEvents bulk") + log.Fatalf("can't create webCustomEvents bulk: %s", err) + } + conn.webClickEvents, err = NewBulk(conn.c, + "events.clicks", + "(session_id, message_id, timestamp, label, selector, url, path)", + "($%d, $%d, $%d, NULLIF($%d, ''), $%d, $%d, $%d)", + 7, 200) + if err != nil { + log.Fatalf("can't create webClickEvents bulk: %s", err) + } + conn.webNetworkRequest, err = NewBulk(conn.c, + "events_common.requests", + "(session_id, timestamp, seq_index, url, host, path, query, request_body, response_body, status_code, method, duration, success)", + "($%d, $%d, $%d, left($%d, 2700), $%d, $%d, $%d, $%d, $%d, $%d::smallint, NULLIF($%d, '')::http_method, $%d, $%d)", + 13, 200) + if err != nil { + log.Fatalf("can't create webNetworkRequest bulk: %s", err) } } @@ -296,6 +314,12 @@ func (conn *Conn) sendBulks() { if err := conn.webCustomEvents.Send(); err != nil { log.Printf("webCustomEvents bulk send err: %s", err) } + if err := conn.webClickEvents.Send(); err != nil { + log.Printf("webClickEvents bulk send err: %s", err) + } + if err := conn.webNetworkRequest.Send(); err != nil { + log.Printf("webNetworkRequest bulk send err: %s", err) + } } func (conn *Conn) CommitBatches() { diff --git a/backend/pkg/db/postgres/messages-web.go b/backend/pkg/db/postgres/messages-web.go index ad0210694..63669ecb7 100644 --- a/backend/pkg/db/postgres/messages-web.go +++ b/backend/pkg/db/postgres/messages-web.go @@ -58,16 +58,12 @@ func (conn *Conn) InsertWebPageEvent(sessionID uint64, projectID uint32, e *Page } func (conn *Conn) InsertWebClickEvent(sessionID uint64, projectID uint32, e *ClickEvent) error { - sqlRequest := ` - INSERT INTO events.clicks - (session_id, message_id, timestamp, label, selector, url) - (SELECT - $1, $2, $3, NULLIF($4, ''), $5, host || path - FROM events.pages - WHERE session_id = $1 AND timestamp <= $3 ORDER BY timestamp DESC LIMIT 1 - ) - ` - conn.batchQueue(sessionID, sqlRequest, sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Label, e.Selector) + var host, path string + host, path, _, _ = url.GetURLParts(e.Url) + log.Println("insert web click:", host, path) + if err := conn.webClickEvents.Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Label, e.Selector, host+path, path); err != nil { + log.Printf("insert web click err: %s", err) + } // Accumulate session updates and exec inside batch with another sql commands conn.updateSessionEvents(sessionID, 1, 0) // Add new value set to autocomplete bulk @@ -119,29 +115,8 @@ func (conn *Conn) InsertWebNetworkRequest(sessionID uint64, projectID uint32, sa if err != nil { return err } - - sqlRequest := ` - INSERT INTO events_common.requests ( - session_id, timestamp, seq_index, - url, host, path, query, - request_body, response_body, status_code, method, - duration, success - ) VALUES ( - $1, $2, $3, - left($4, 2700), $5, $6, $7, - $8, $9, $10::smallint, NULLIF($11, '')::http_method, - $12, $13 - ) ON CONFLICT DO NOTHING` - conn.batchQueue(sessionID, sqlRequest, - sessionID, e.Meta().Timestamp, truncSqIdx(e.Meta().Index), - e.URL, host, path, query, - request, response, e.Status, url.EnsureMethod(e.Method), - e.Duration, e.Status < 400, - ) - - // Record approximate message size - conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.URL)+len(host)+len(path)+len(query)+ - len(e.Request)+len(e.Response)+len(url.EnsureMethod(e.Method))+8*5+1) + conn.webNetworkRequest.Append(sessionID, e.Meta().Timestamp, truncSqIdx(e.Meta().Index), e.URL, host, path, query, + request, response, e.Status, url.EnsureMethod(e.Method), e.Duration, e.Status < 400) return nil } diff --git a/backend/pkg/handlers/web/networkIssue.go b/backend/pkg/handlers/web/networkIssue.go index 5e7119c20..20ef412dd 100644 --- a/backend/pkg/handlers/web/networkIssue.go +++ b/backend/pkg/handlers/web/networkIssue.go @@ -19,20 +19,6 @@ func (f *NetworkIssueDetector) Build() Message { func (f *NetworkIssueDetector) Handle(message Message, messageID uint64, timestamp uint64) Message { switch msg := message.(type) { - // case *ResourceTiming: - // success := msg.Duration != 0 // The only available way here - // if !success { - // issueType := "missing_resource" - // if msg.Initiator == "fetch" || msg.Initiator == "xmlhttprequest" { - // issueType = "bad_request" - // } - // return &IssueEvent{ - // Type: issueType, - // MessageID: messageID, - // Timestamp: msg.Timestamp, - // ContextString: msg.URL, - // } - // } case *NetworkRequest: if msg.Status >= 400 { return &IssueEvent{ diff --git a/backend/pkg/messages/cache.go b/backend/pkg/messages/cache.go new file mode 100644 index 000000000..b3c83a0e3 --- /dev/null +++ b/backend/pkg/messages/cache.go @@ -0,0 +1,22 @@ +package messages + +type pageLocations struct { + urls map[uint64]string +} + +func NewPageLocations() *pageLocations { + return &pageLocations{urls: make(map[uint64]string)} +} + +func (p *pageLocations) Set(sessID uint64, url string) { + p.urls[sessID] = url +} + +func (p *pageLocations) Get(sessID uint64) string { + url := p.urls[sessID] + return url +} + +func (p *pageLocations) Delete(sessID uint64) { + delete(p.urls, sessID) +} diff --git a/backend/pkg/messages/iterator.go b/backend/pkg/messages/iterator.go index 86e3e6cc8..69e1f02bc 100644 --- a/backend/pkg/messages/iterator.go +++ b/backend/pkg/messages/iterator.go @@ -24,10 +24,15 @@ type messageIteratorImpl struct { broken bool messageInfo *message batchInfo *BatchInfo + urls *pageLocations } func NewMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { - iter := &messageIteratorImpl{handler: messageHandler, autoDecode: autoDecode} + iter := &messageIteratorImpl{ + handler: messageHandler, + autoDecode: autoDecode, + urls: NewPageLocations(), + } if len(messageFilter) != 0 { filter := make(map[int]struct{}, len(messageFilter)) for _, msgType := range messageFilter { @@ -125,7 +130,7 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("BatchMetadata") } - i.messageInfo.Url = m.Url + i.messageInfo.Url = m.Location i.version = m.Version i.batchInfo.version = m.Version @@ -138,6 +143,10 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("BatchMeta") } + // Try to get saved session's page url + if savedURL := i.urls.Get(i.messageInfo.batch.sessionID); savedURL != "" { + i.messageInfo.Url = savedURL + } case *Timestamp: i.messageInfo.Timestamp = int64(m.Timestamp) @@ -158,9 +167,13 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("SessionEnd") } + // Delete session from urls cache layer + i.urls.Delete(i.messageInfo.batch.sessionID) case *SetPageLocation: i.messageInfo.Url = m.URL + // Save session page url in cache for using in next batches + i.urls.Set(i.messageInfo.batch.sessionID, m.URL) } return nil } From d5fc6d7a1b691d3ed18be17fba2127c1f0592d58 Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 26 Dec 2022 16:29:20 +0100 Subject: [PATCH 04/25] change(ui): add api client response check; add types for some immut collections --- frontend/app/api_client.js | 11 +- .../FilterAutoComplete/FilterAutoComplete.tsx | 8 +- frontend/app/duck/{index.js => index.ts} | 8 +- frontend/app/duck/user.js | 1 - frontend/app/services/ErrorService.ts | 11 +- frontend/app/services/MetricService.ts | 14 +-- frontend/app/services/NotesService.ts | 100 +++++++----------- frontend/app/services/RecordingsService.ts | 28 ----- frontend/app/services/SessionService.ts | 11 +- frontend/app/services/UserService.ts | 14 +-- .../types/account/{account.js => account.ts} | 24 ++++- .../app/types/account/{limit.js => limit.ts} | 10 ++ frontend/app/types/{member.js => member.ts} | 14 +++ frontend/app/utils/index.ts | 7 -- 14 files changed, 129 insertions(+), 132 deletions(-) rename frontend/app/duck/{index.js => index.ts} (93%) rename frontend/app/types/account/{account.js => account.ts} (57%) rename frontend/app/types/account/{limit.js => limit.ts} (61%) rename frontend/app/types/{member.js => member.ts} (75%) diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 8985d0686..b60c0dbb7 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -95,7 +95,16 @@ export default class APIClient { ) { edp = `${ edp }/${ this.siteId }` } - return fetch(edp + path, this.init); + return fetch(edp + path, this.init) + .then(response => { + if (response.ok) { + return response + } else { + throw new Error( + `! ${this.init.method} error on ${path}; ${response.status}` + ) + } + }) } get(path, params, options) { diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index a616d8853..b80214bd8 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -145,17 +145,17 @@ function FilterAutoComplete(props: Props) { new APIClient() [method?.toLocaleLowerCase()](endpoint, { ...params, q: inputValue }) .then((response: any) => { - if (response.ok) { return response.json(); - } - throw new Error(response.statusText); }) .then(({ data }: any) => { const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || []; setOptions(_options); callback(_options); setLoading(false); - }); + }) + .catch((e) => { + throw new Error(e); + }) }; const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params]); diff --git a/frontend/app/duck/index.js b/frontend/app/duck/index.ts similarity index 93% rename from frontend/app/duck/index.js rename to frontend/app/duck/index.ts index 051ec6933..fd4b0a655 100644 --- a/frontend/app/duck/index.js +++ b/frontend/app/duck/index.ts @@ -3,7 +3,6 @@ import { combineReducers } from 'redux-immutable'; import jwt from './jwt'; import user from './user'; import sessions from './sessions'; -import issues from './issues'; import assignments from './assignments'; import target from './target'; import targetCustom from './targetCustom'; @@ -34,11 +33,10 @@ import customMetrics from './customMetrics'; import search from './search'; import liveSearch from './liveSearch'; -export default combineReducers({ +const rootReducer = combineReducers({ jwt, user, sessions, - issues, assignments, target, targetCustom, @@ -70,3 +68,7 @@ export default combineReducers({ ...integrations, ...sources, }); + +export type RootStore = ReturnType + +export default rootReducer \ No newline at end of file diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index 1420cae45..5b39d34db 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -21,7 +21,6 @@ const PUSH_NEW_SITE = 'user/PUSH_NEW_SITE'; const SET_ONBOARDING = 'user/SET_ONBOARDING'; const initialState = Map({ - // client: Client(), account: Account(), siteId: null, passwordRequestError: false, diff --git a/frontend/app/services/ErrorService.ts b/frontend/app/services/ErrorService.ts index 5b3520b5c..9e9402b45 100644 --- a/frontend/app/services/ErrorService.ts +++ b/frontend/app/services/ErrorService.ts @@ -1,16 +1,17 @@ import BaseService from './BaseService'; -import { fetchErrorCheck } from 'App/utils' export default class ErrorService extends BaseService { all(params: any = {}): Promise { return this.client.post('/errors/search', params) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || []); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || []) + .catch(e => Promise.reject(e)) } one(id: string): Promise { return this.client.get(`/errors/${id}`) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } \ No newline at end of file diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index a5734aff0..966b3668f 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -1,6 +1,5 @@ import Widget from "App/mstore/types/widget"; import APIClient from 'App/api_client'; -import { fetchErrorCheck } from "App/utils"; export default class MetricService { private client: APIClient; @@ -30,8 +29,9 @@ export default class MetricService { */ getMetric(metricId: string): Promise { return this.client.get('/metrics/' + metricId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** @@ -45,8 +45,9 @@ export default class MetricService { const method = isCreating ? 'post' : 'put'; const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; return this.client[method](url, data) - .then(fetchErrorCheck) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** @@ -74,8 +75,9 @@ export default class MetricService { getMetricChartData(metric: Widget, data: any, isWidget: boolean = false): Promise { const path = isWidget ? `/metrics/${metric.metricId}/chart` : `/metrics/try`; return this.client.post(path, data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** diff --git a/frontend/app/services/NotesService.ts b/frontend/app/services/NotesService.ts index 97534973e..cdecb7c3f 100644 --- a/frontend/app/services/NotesService.ts +++ b/frontend/app/services/NotesService.ts @@ -45,89 +45,61 @@ export interface NotesFilter { } export default class NotesService { - private client: APIClient; + private client: APIClient; - constructor(client?: APIClient) { - this.client = client ? client : new APIClient(); - } + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } - initClient(client?: APIClient) { - this.client = client || new APIClient(); - } + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } - fetchNotes(filter: NotesFilter): Promise { - return this.client.post('/notes', filter).then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes: ' + r.status) - } - }) - } + fetchNotes(filter: NotesFilter): Promise { + return this.client.post('/notes', filter).then(r => { + return r.json().then(r => r.data) + }) + } - getNotesBySessionId(sessionID: string): Promise { - return this.client.get(`/sessions/${sessionID}/notes`) + getNotesBySessionId(sessionID: string): Promise { + return this.client.get(`/sessions/${sessionID}/notes`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes for ' +sessionID + ' cuz: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - addNote(sessionID: string, note: WriteNote): Promise { - return this.client.post(`/sessions/${sessionID}/notes`, note) + addNote(sessionID: string, note: WriteNote): Promise { + return this.client.post(`/sessions/${sessionID}/notes`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error adding note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - updateNote(noteID: string, note: WriteNote): Promise { - return this.client.post(`/notes/${noteID}`, note) + updateNote(noteID: string, note: WriteNote): Promise { + return this.client.post(`/notes/${noteID}`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error updating note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - deleteNote(noteID: number) { - return this.client.delete(`/notes/${noteID}`) + deleteNote(noteID: number) { + return this.client.delete(`/notes/${noteID}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error deleting note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - sendSlackNotification(noteId: string, webhook: string) { - return this.client.get(`/notes/${noteId}/slack/${webhook}`) + sendSlackNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/slack/${webhook}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error sending slack notif: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - sendMsTeamsNotification(noteId: string, webhook: string) { - return this.client.get(`/notes/${noteId}/msteams/${webhook}`) + sendMsTeamsNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/msteams/${webhook}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error sending slack notif: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } } diff --git a/frontend/app/services/RecordingsService.ts b/frontend/app/services/RecordingsService.ts index f7885a36b..6dd3845c5 100644 --- a/frontend/app/services/RecordingsService.ts +++ b/frontend/app/services/RecordingsService.ts @@ -37,11 +37,7 @@ export default class RecordingsService { reserveUrl(siteId: string, recordingData: RecordingData): Promise<{ URL: string; key: string }> { return this.client.put(`/${siteId}/assist/save`, recordingData).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't reserve space for recording: " + r.status); - } }); } @@ -51,61 +47,37 @@ export default class RecordingsService { headers: { 'Content-Type': 'video/webm' }, body: file, }).then((r) => { - if (r.ok) { return true; - } else { - throw new Error("Can't upload file: " + r.status); - } }); } confirmFile(siteId: string, recordingData: RecordingData, key: string): Promise { return this.client.put(`/${siteId}/assist/save/done`, { ...recordingData, key }).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't confirm file saving: " + r.status); - } }); } fetchRecordings(filters: FetchFilter): Promise { return this.client.post(`/assist/records`, filters).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } fetchRecording(id: number): Promise { return this.client.get(`/assist/records/${id}`).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } updateRecordingName(id: number, name: string): Promise { return this.client.post(`/assist/records/${id}`, { name }).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } deleteRecording(id: number): Promise { return this.client.delete(`/assist/records/${id}`).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } } diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts index 0ea0468b2..6f59b3805 100644 --- a/frontend/app/services/SessionService.ts +++ b/frontend/app/services/SessionService.ts @@ -1,5 +1,4 @@ import APIClient from 'App/api_client'; -import { fetchErrorCheck } from 'App/utils'; export default class SettingsService { private client: APIClient; @@ -26,8 +25,9 @@ export default class SettingsService { getSessions(filter: any) { return this.client .post('/sessions/search', filter) - .then(fetchErrorCheck) - .then((response) => response.data || []); + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) } getSessionInfo(sessionId: string, isLive?: boolean): Promise> { @@ -41,7 +41,8 @@ export default class SettingsService { getLiveSessions(filter: any) { return this.client .post('/assist/sessions', filter) - .then(fetchErrorCheck) - .then((response) => response.data || []); + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) } } diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts index 7a515d910..705040eb2 100644 --- a/frontend/app/services/UserService.ts +++ b/frontend/app/services/UserService.ts @@ -1,6 +1,5 @@ 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; @@ -29,12 +28,14 @@ export default class UserService { const data = user.toSave(); if (user.userId) { return this.client.put('/client/members/' + user.userId, data) - .then(fetchErrorCheck) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } else { return this.client.post('/client/members', data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } @@ -46,8 +47,9 @@ export default class UserService { delete(userId: string) { return this.client.delete('/client/members/' + userId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } getRoles() { diff --git a/frontend/app/types/account/account.js b/frontend/app/types/account/account.ts similarity index 57% rename from frontend/app/types/account/account.js rename to frontend/app/types/account/account.ts index bb1c058cd..00f4e30d4 100644 --- a/frontend/app/types/account/account.js +++ b/frontend/app/types/account/account.ts @@ -1,7 +1,27 @@ -import Member from 'Types/member'; -import Limit from './limit'; +import Member, { IMember } from 'Types/member'; +import Limit, { ILimits } from './limit'; import { DateTime } from 'luxon'; +// TODO types for mobx and all +export interface IAccount extends IMember { + changePassword?: any + limits: ILimits + banner: string + email: string + verifiedEmail: string + id: string + smtp: boolean + license: string + expirationDate?: DateTime + permissions: string[] + iceServers: string + hasPassword: boolean + apiKey: string + tenantKey: string + edition: string + optOut: string +} + export default Member.extend({ changePassword: undefined, limits: Limit(), diff --git a/frontend/app/types/account/limit.js b/frontend/app/types/account/limit.ts similarity index 61% rename from frontend/app/types/account/limit.js rename to frontend/app/types/account/limit.ts index f5274f7b0..7ded53b51 100644 --- a/frontend/app/types/account/limit.js +++ b/frontend/app/types/account/limit.ts @@ -1,6 +1,16 @@ import Record from 'Types/Record'; import { Map } from 'immutable'; +interface ILimitValue { + limit: number + remaining: number +} + +export interface ILimits { + teamMember: ILimitValue + sites: ILimitValue +} + const defaultValues = Map({ limit: 0, remaining: 0 }); const Limit = Record({ teamMember: defaultValues, diff --git a/frontend/app/types/member.js b/frontend/app/types/member.ts similarity index 75% rename from frontend/app/types/member.js rename to frontend/app/types/member.ts index 8e15397f6..d3914eac9 100644 --- a/frontend/app/types/member.js +++ b/frontend/app/types/member.ts @@ -2,6 +2,20 @@ import Record from 'Types/Record'; import { DateTime } from 'luxon'; import { validateEmail, validateName } from 'App/validate'; +export interface IMember { + id: string + name: string + email: string + createdAt: DateTime + admin: boolean + superAdmin: boolean + joined: boolean + expiredInvitation: boolean + roleId: string + roleName: string + invitationLink: string +} + export default Record({ id: undefined, name: '', diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts index 741e91e8a..b9ab9f8bf 100644 --- a/frontend/app/utils/index.ts +++ b/frontend/app/utils/index.ts @@ -346,13 +346,6 @@ export const exportCSVFile = (headers, items, fileTitle) => { } }; -export const fetchErrorCheck = async (response: any) => { - if (!response.ok) { - return Promise.reject(response); - } - return response.json(); -}; - export const cleanSessionFilters = (data: any) => { const { filters, ...rest } = data; const _fitlers = filters.filter((f: any) => { From 72cc89c50946347aa9b33a9761641e66775b6c8e Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Mon, 26 Dec 2022 16:34:12 +0100 Subject: [PATCH 05/25] ci(actions): Directly push scanned images Signed-off-by: rjshrjndrn --- .github/workflows/api-ee.yaml | 2 +- .github/workflows/api.yaml | 2 +- .github/workflows/workers-ee.yaml | 5 ++--- .github/workflows/workers.yaml | 5 ++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index 9eb23910a..a6a3998cb 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -67,7 +67,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh ee + docker push $DOCKER_REPO/$image:$IMAGE_TAG - name: Creating old image input run: | # diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index e4d85ff24..e85775ed2 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -66,7 +66,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh + docker push $DOCKER_REPO/$image:$IMAGE_TAG - name: Creating old image input run: | # diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index c573dff26..3f32314f9 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -95,6 +95,7 @@ jobs: # Pushing image to registry # cd backend + cat /tmp/images_to_build.txt for image in $(cat /tmp/images_to_build.txt); do echo "Bulding $image" @@ -109,7 +110,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh ee $image + docker push $DOCKER_REPO/$image:$IMAGE_TAG echo "::set-output name=image::$DOCKER_REPO/$image:$IMAGE_TAG" done @@ -156,8 +157,6 @@ jobs: mv /tmp/helmcharts/* openreplay/charts/ ls openreplay/charts - cat /tmp/image_override.yaml - # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index e222e00fb..21fc55c06 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -95,6 +95,7 @@ jobs: # Pushing image to registry # cd backend + cat /tmp/images_to_build.txt for image in $(cat /tmp/images_to_build.txt); do echo "Bulding $image" @@ -109,7 +110,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh skip $image + docker push $DOCKER_REPO/$image:$IMAGE_TAG echo "::set-output name=image::$DOCKER_REPO/$image:$IMAGE_TAG" done @@ -154,8 +155,6 @@ jobs: mv /tmp/helmcharts/* openreplay/charts/ ls openreplay/charts - cat /tmp/image_override.yaml - # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - From ed7f979f678b1ee86ffe6f6c10d0902f97576f9f Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 18:22:55 +0100 Subject: [PATCH 06/25] chore(tracker): update ts version --- tracker/tracker/package.json | 2 +- tracker/tracker/src/main/tsconfig.json | 9 +++------ tracker/tracker/src/webworker/tsconfig.json | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index c18649060..5c560150c 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -44,7 +44,7 @@ "rollup-plugin-terser": "^6.1.0", "semver": "^6.3.0", "ts-jest": "^29.0.3", - "typescript": "4.6.0-dev.20211126" + "typescript": "^4.9.4" }, "dependencies": { "error-stack-parser": "^2.0.6" diff --git a/tracker/tracker/src/main/tsconfig.json b/tracker/tracker/src/main/tsconfig.json index 14a932c39..5bc2f3a50 100644 --- a/tracker/tracker/src/main/tsconfig.json +++ b/tracker/tracker/src/main/tsconfig.json @@ -1,11 +1,8 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "allowSyntheticDefaultImports": true, - "lib": ["es6", "dom"], + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"] }, - "references": [ - { "path": "../common" } - ], - "exclude": ["app/observer"] + "references": [{ "path": "../common" }] } diff --git a/tracker/tracker/src/webworker/tsconfig.json b/tracker/tracker/src/webworker/tsconfig.json index 52228f9af..5506aa82b 100644 --- a/tracker/tracker/src/webworker/tsconfig.json +++ b/tracker/tracker/src/webworker/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "lib": ["es6", "webworker"] + "lib": ["es6"] }, "references": [{ "path": "../common" }] } From dcf7f0806fdce4592821e6cf599364e6090d3a6a Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 18:23:04 +0100 Subject: [PATCH 07/25] chore(tracker):test file extension resolver fix --- tracker/tracker/jest.config.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tracker/tracker/jest.config.js b/tracker/tracker/jest.config.js index ac058f12c..93fae253a 100644 --- a/tracker/tracker/jest.config.js +++ b/tracker/tracker/jest.config.js @@ -1,5 +1,11 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +const config = { preset: 'ts-jest', testEnvironment: 'jsdom', + // .js file extension fix + moduleNameMapper: { + '(.+)\\.js': '$1', + }, } + +export default config From d2923a5cb8659734e3ed223f1f6184ca622338af Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 18:26:04 +0100 Subject: [PATCH 08/25] style(tests): removed unnecessary gitignore files --- tests/.gitignore | 1 - tests/e2e/.gitignore | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 tests/.gitignore delete mode 100644 tests/e2e/.gitignore diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 5bd24dc36..000000000 --- a/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -e2e/node_modules \ No newline at end of file diff --git a/tests/e2e/.gitignore b/tests/e2e/.gitignore deleted file mode 100644 index 84f82b8b3..000000000 --- a/tests/e2e/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.log -node_modules/ -.idea -*.DS_Store From 5cc74df0af507e52e94a585b11464dc57bfc5d68 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 21:30:00 +0100 Subject: [PATCH 09/25] refactor(player):initialize assistManager for live only --- frontend/app/player/create.ts | 8 +++--- frontend/app/player/web/WebLivePlayer.ts | 32 +++++++++--------------- frontend/app/player/web/WebPlayer.ts | 14 +++-------- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index a69d9710e..39114d7cb 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -2,10 +2,12 @@ import SimpleStore from './common/SimpleStore' import type { Store } from './common/types' import WebPlayer from './web/WebPlayer' +import WebLivePlayer from './web/WebLivePlayer' type WebState = typeof WebPlayer.INITIAL_STATE //? type WebPlayerStore = Store export type IWebPlayer = WebPlayer +export type IWebLivePlayer = WebLivePlayer export type IWebPlayerStore = WebPlayerStore export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { @@ -16,12 +18,12 @@ export function createWebPlayer(session: Record, wrapStore?: (s:IWe store = wrapStore(store) } - const player = new WebPlayer(store, session, null, false) + const player = new WebPlayer(store, session, false) return [player, store] } -export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebLivePlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...WebPlayer.INITIAL_STATE, }) @@ -29,6 +31,6 @@ export function createLiveWebPlayer(session: Record, config: RTCIce store = wrapStore(store) } - const player = new WebPlayer(store, session, config, true) + const player = new WebLivePlayer(store, session, config) return [player, store] } diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index d7b707b1c..8cd41ab15 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -1,23 +1,15 @@ -// import WebPlayer from './WebPlayer' -// import AssistManager from './assist/AssistManager' +import type { Store } from '../common/types' + +import WebPlayer from './WebPlayer' +import AssistManager from './assist/AssistManager' -// export default class WebLivePlayer extends WebPlayer { -// assistManager: AssistManager // public so far -// constructor(private wpState: Store, session, config: RTCIceServer[]) { -// super(wpState) -// this.assistManager = new AssistManager(session, this.messageManager, config, wpState) -// const endTime = !live && session.duration.valueOf() -// wpState.update({ -// //@ts-ignore -// initialized: true, -// //@ts-ignore -// session, - -// live: true, -// livePlay: true, -// }) +export default class WebLivePlayer extends WebPlayer { + assistManager: AssistManager // public so far + constructor(wpState: Store, session:any, config: RTCIceServer[]) { + super(wpState, session, true) -// this.assistManager.connect(session.agentToken) -// } -// } \ No newline at end of file + this.assistManager = new AssistManager(session, this.messageManager, this.screen, config, wpState) + this.assistManager.connect(session.agentToken) + } +} \ No newline at end of file diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index bd80c1189..adefc65be 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -23,14 +23,13 @@ export default class WebPlayer extends Player { liveTimeTravel: false, } - private readonly screen: Screen private readonly inspectorController: InspectorController + protected readonly screen: Screen protected readonly messageManager: MessageManager - assistManager: AssistManager // public so far private targetMarker: TargetMarker - constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { + constructor(private wpState: Store, session: any, live: boolean) { let initialLists = live ? {} : { event: session.events.toJSON(), stack: session.stackEvents.toJSON(), @@ -55,10 +54,8 @@ export default class WebPlayer extends Player { this.inspectorController = new InspectorController(screen) - const endTime = !live && session.duration.valueOf() + const endTime = session.duration?.valueOf() || 0 wpState.update({ - //@ts-ignore - initialized: true, //@ts-ignore session, @@ -67,11 +64,6 @@ export default class WebPlayer extends Player { endTime, // : 0, }) - // TODO: separate LiveWebPlayer - this.assistManager = new AssistManager(session, this.messageManager, screen, config, wpState) - if (live) { - this.assistManager.connect(session.agentToken) - } } attach = (parent: HTMLElement) => { From 33a6f854a2681d7035e31130ab0ca12fa2d000b1 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 21:30:40 +0100 Subject: [PATCH 10/25] style(player):small code style fix --- frontend/app/player/web/MessageManager.ts | 6 ++---- frontend/app/player/web/messages/MFileReader.ts | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index e548f406e..8f0920eb4 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -144,10 +144,8 @@ export default class MessageManager { private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { const msgs: Array = [] - let next: ReturnType - while (next = fileReader.next()) { - const [msg, index] = next - this.distributeMessage(msg, index) + for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { + this.distributeMessage(msg, msg._index) msgs.push(msg) onMessage?.(msg) } diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts index 5c8ddbf30..8c753979c 100644 --- a/frontend/app/player/web/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -49,7 +49,7 @@ export default class MFileReader extends RawMessageReader { } } - next(): [ Message, number] | null { + readNext(): Message & { _index: number } | null { if (this.error || !this.hasNextByte()) { return null } @@ -74,7 +74,7 @@ export default class MFileReader extends RawMessageReader { this.startTime = rMsg.timestamp } this.currentTime = rMsg.timestamp - this.startTime - return this.next() + return this.readNext() } const index = this.getLastMessageID() @@ -83,6 +83,6 @@ export default class MFileReader extends RawMessageReader { _index: index, }) - return [msg, index] + return msg } } From 3b43691945027328fff2a7d2e62a51062ee6a9fd Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Mon, 26 Dec 2022 22:22:31 +0100 Subject: [PATCH 11/25] refactor(player): separate more live functions to WebLivePlayer --- frontend/app/player/create.ts | 14 +++++++++----- frontend/app/player/web/WebLivePlayer.ts | 19 ++++++++++++++++++- frontend/app/player/web/WebPlayer.ts | 19 +------------------ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index 39114d7cb..a07f3910d 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -4,12 +4,16 @@ import type { Store } from './common/types' import WebPlayer from './web/WebPlayer' import WebLivePlayer from './web/WebLivePlayer' -type WebState = typeof WebPlayer.INITIAL_STATE //? +type WebState = typeof WebPlayer.INITIAL_STATE type WebPlayerStore = Store export type IWebPlayer = WebPlayer -export type IWebLivePlayer = WebLivePlayer export type IWebPlayerStore = WebPlayerStore +type WebLiveState = typeof WebLivePlayer.INITIAL_STATE +type WebLivePlayerStore = Store +export type IWebLivePlayer = WebLivePlayer +export type IWebLivePlayerStore = WebLivePlayerStore + export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...WebPlayer.INITIAL_STATE, @@ -23,9 +27,9 @@ export function createWebPlayer(session: Record, wrapStore?: (s:IWe } -export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebLivePlayer, IWebPlayerStore] { - let store: WebPlayerStore = new SimpleStore({ - ...WebPlayer.INITIAL_STATE, +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebLivePlayerStore) => IWebLivePlayerStore): [IWebLivePlayer, IWebLivePlayerStore] { + let store: WebLivePlayerStore = new SimpleStore({ + ...WebLivePlayer.INITIAL_STATE, }) if (wrapStore) { store = wrapStore(store) diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index 8cd41ab15..6ac2665a1 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -5,11 +5,28 @@ import AssistManager from './assist/AssistManager' export default class WebLivePlayer extends WebPlayer { + static readonly INITIAL_STATE = { + ...WebPlayer.INITIAL_STATE, + ...AssistManager.INITIAL_STATE, + liveTimeTravel: false, + } + assistManager: AssistManager // public so far - constructor(wpState: Store, session:any, config: RTCIceServer[]) { + constructor(wpState: Store, session:any, config: RTCIceServer[]) { super(wpState, session, true) this.assistManager = new AssistManager(session, this.messageManager, this.screen, config, wpState) this.assistManager.connect(session.agentToken) } + + // TODO separate message receivers + toggleTimetravel = async () => { + if (!this.wpState.get().liveTimeTravel) { + await this.messageManager.reloadWithUnprocessedFile(() => + this.wpState.update({ + liveTimeTravel: true, + }) + ) + } + } } \ No newline at end of file diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index adefc65be..83b82d0b6 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -6,7 +6,6 @@ import Player, { State as PlayerState } from '../player/Player' import MessageManager from './MessageManager' import InspectorController from './addons/InspectorController' import TargetMarker from './addons/TargetMarker' -import AssistManager from './assist/AssistManager' import Screen from './Screen/Screen' // export type State = typeof WebPlayer.INITIAL_STATE @@ -15,12 +14,9 @@ export default class WebPlayer extends Player { static readonly INITIAL_STATE = { ...Player.INITIAL_STATE, ...TargetMarker.INITIAL_STATE, - ...MessageManager.INITIAL_STATE, - ...AssistManager.INITIAL_STATE, inspectorMode: false, - liveTimeTravel: false, } private readonly inspectorController: InspectorController @@ -29,7 +25,7 @@ export default class WebPlayer extends Player { private targetMarker: TargetMarker - constructor(private wpState: Store, session: any, live: boolean) { + constructor(protected wpState: Store, session: any, live: boolean) { let initialLists = live ? {} : { event: session.events.toJSON(), stack: session.stackEvents.toJSON(), @@ -110,25 +106,12 @@ export default class WebPlayer extends Player { this.targetMarker.markTargets(...args) } - - // TODO separate message receivers - toggleTimetravel = async () => { - if (!this.wpState.get().liveTimeTravel) { - await this.messageManager.reloadWithUnprocessedFile(() => - this.wpState.update({ - liveTimeTravel: true, - }) - ) - } - } - toggleUserName = (name?: string) => { this.screen.cursor.showTag(name) } clean = () => { super.clean() - this.assistManager.clean() window.removeEventListener('resize', this.scale) } } From 41031c9f3769b04419f23d35e0bfe189dbc09545 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 27 Dec 2022 00:32:53 +0100 Subject: [PATCH 12/25] refactor(player): move all live timeTravel logic to the WebLivePlayer --- frontend/app/player/web/MessageManager.ts | 66 ++++------------- frontend/app/player/web/WebLivePlayer.ts | 70 ++++++++++++++++--- .../app/player/web/assist/AssistManager.ts | 12 ++-- .../app/player/web/messages/MStreamReader.ts | 2 +- 4 files changed, 80 insertions(+), 70 deletions(-) diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 8f0920eb4..0c52350d4 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -116,7 +116,6 @@ export default class MessageManager { private sessionStart: number; private navigationStartOffset: number = 0; private lastMessageTime: number = 0; - private lastMessageInFileTime: number = 0; constructor( private readonly session: any /*Session*/, @@ -142,17 +141,7 @@ export default class MessageManager { this.loadMessages() } - private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { - const msgs: Array = [] - for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { - this.distributeMessage(msg, msg._index) - msgs.push(msg) - onMessage?.(msg) - } - - logger.info("Messages count: ", msgs.length, msgs) - - + private _sortMessagesHack(msgs: Message[]) { // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); this.pagesManager.sortPages((m1, m2) => { @@ -179,7 +168,6 @@ export default class MessageManager { }) } - private waitingForFiles: boolean = false private onFileReadSuccess = () => { const stateToUpdate : Partial= { @@ -199,10 +187,6 @@ export default class MessageManager { toast.error('Error requesting a session file') } private onFileReadFinally = () => { - this.incomingMessages - .filter(msg => msg.time >= this.lastMessageInFileTime) - .forEach(msg => this.distributeMessage(msg, 0)) - this.waitingForFiles = false this.setMessagesLoading(false) } @@ -217,7 +201,14 @@ export default class MessageManager { const fileReader = new MFileReader(new Uint8Array(), this.sessionStart) return (b: Uint8Array) => decrypt(b).then(b => { fileReader.append(b) - this.parseAndDistributeMessages(fileReader) + const msgs: Array = [] + for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { + this.distributeMessage(msg, msg._index) + msgs.push(msg) + } + + logger.info("Messages count: ", msgs.length, msgs) + this._sortMessagesHack(msgs) this.setMessagesLoading(false) }) } @@ -249,35 +240,14 @@ export default class MessageManager { .then(createNewParser(false)) ) .then(() => { - this.state.update(this.lists.getFullListsState()) + this.state.update(this.lists.getFullListsState()) // TODO: also in case of dynamic update through assist }) .catch(e => logger.error("Can not download the devtools file", e)) .finally(() => this.state.update({ devtoolsLoading: false })) } } - reloadWithUnprocessedFile(onSuccess: () => void) { - const onData = (byteArray: Uint8Array) => { - const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } - this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) - } - - // assist will pause and skip messages to prevent timestamp related errors - this.reloadMessageManagers() - this.windowNodeCounter.reset() - - this.setMessagesLoading(true) - this.waitingForFiles = true - - return requestEFSDom(this.session.sessionId) - .then(onData) - .then(onSuccess) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) - } - - private reloadMessageManagers() { + resetMessageManagers() { this.locationEventManager = new ListWalker(); this.locationManager = new ListWalker(); this.loadedLocationManager = new ListWalker(); @@ -376,16 +346,7 @@ export default class MessageManager { return { ...msg, ...decoded }; } - private readonly incomingMessages: Message[] = [] - appendMessage(msg: Message, index: number) { - //TODO: put index in message type - this.incomingMessages.push(msg) - if (!this.waitingForFiles) { - this.distributeMessage(msg, index) - } - } - - private distributeMessage(msg: Message, index: number): void { + distributeMessage(msg: Message, index: number): void { const lastMessageTime = Math.max(msg.time, this.lastMessageTime) this.lastMessageTime = lastMessageTime this.state.update({ lastMessageTime }) @@ -529,14 +490,11 @@ export default class MessageManager { private setSize({ height, width }: { height: number, width: number }) { this.screen.scale({ height, width }); this.state.update({ width, height }); - - //this.updateMarketTargets() } // TODO: clean managers? clean() { this.state.update(MessageManager.INITIAL_STATE); - this.incomingMessages.length = 0 } } diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index 6ac2665a1..87e88225a 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -1,8 +1,14 @@ import type { Store } from '../common/types' +import type { Message } from './messages' import WebPlayer from './WebPlayer' import AssistManager from './assist/AssistManager' +import MFileReader from './messages/MFileReader' +import { requestEFSDom } from './network/loadFiles' + +import { toast } from 'react-toastify'; // ** + export default class WebLivePlayer extends WebPlayer { static readonly INITIAL_STATE = { @@ -12,21 +18,67 @@ export default class WebLivePlayer extends WebPlayer { } assistManager: AssistManager // public so far - constructor(wpState: Store, session:any, config: RTCIceServer[]) { + private readonly incomingMessages: Message[] = [] + private historyFileIsLoading = false + private lastMessageInFileTime = 0 + private lastMessageInFileIndex = 0 + + constructor(wpState: Store, private session:any, config: RTCIceServer[]) { super(wpState, session, true) - this.assistManager = new AssistManager(session, this.messageManager, this.screen, config, wpState) + this.assistManager = new AssistManager( + session, + f => this.messageManager.setCSSLoading(f), + (msg, idx) => { + this.incomingMessages.push(msg) + if (!this.historyFileIsLoading) { + // TODO: fix index-ing after historyFile-load + this.messageManager.distributeMessage(msg, idx) + } + }, + this.screen, + config, + wpState, + ) this.assistManager.connect(session.agentToken) } - // TODO separate message receivers toggleTimetravel = async () => { - if (!this.wpState.get().liveTimeTravel) { - await this.messageManager.reloadWithUnprocessedFile(() => - this.wpState.update({ - liveTimeTravel: true, - }) - ) + if (this.wpState.get().liveTimeTravel) { + return } + this.historyFileIsLoading = true + this.messageManager.setMessagesLoading(true) // do it in one place. update unique loading states each time instead + this.messageManager.resetMessageManagers() + + try { + const bytes = await requestEFSDom(this.session.sessionId) + const fileReader = new MFileReader(bytes, this.session.startedAt) + for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { + this.messageManager.distributeMessage(msg, msg._index) + } + this.wpState.update({ + liveTimeTravel: true, + }) + // here we need to update also lists state, if we gonna use them this.messageManager.onFileReadSuccess + } catch(e) { + toast.error('Error requesting a session file') + console.error("EFS file download error:", e) + } + + // Append previously received messages + this.incomingMessages + .filter(msg => msg.time >= this.lastMessageInFileTime) + .forEach((msg, i) => this.messageManager.distributeMessage(msg, this.lastMessageInFileIndex + i)) + this.incomingMessages.length = 0 + + this.historyFileIsLoading = false + this.messageManager.setMessagesLoading(false) + } + + clean = () => { + this.incomingMessages.length = 0 + this.assistManager.clean() + super.clean() } } \ No newline at end of file diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index 25763ef76..ec41e5382 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -1,7 +1,7 @@ import type { Socket } from 'socket.io-client'; -import type MessageManager from '../MessageManager'; import type Screen from '../Screen/Screen'; import type { Store } from '../../common/types' +import type { Message } from '../messages'; import MStreamReader from '../messages/MStreamReader'; import JSONRawMessageReader from '../messages/JSONRawMessageReader' import appStore from 'App/store'; @@ -64,7 +64,8 @@ export default class AssistManager { // TODO: Session type constructor( private session: any, - private md: MessageManager, + private setMessagesLoading: (flag: boolean) => void, + private handleMessage: (m: Message, index: number) => void, private screen: Screen, private config: RTCIceServer[], private store: Store, @@ -88,9 +89,9 @@ export default class AssistManager { } if (status === ConnectionStatus.Connecting) { - this.md.setMessagesLoading(true); + this.setMessagesLoading(true); } else { - this.md.setMessagesLoading(false); + this.setMessagesLoading(false); } if (status === ConnectionStatus.Connected) { this.screen.display(true); @@ -174,8 +175,7 @@ export default class AssistManager { } for (let msg = reader.readNext();msg !== null;msg = reader.readNext()) { - // @ts-ignore TODO: make index a thing. - this.md.appendMessage(msg, msg._index) + this.handleMessage(msg, msg._index) } }) diff --git a/frontend/app/player/web/messages/MStreamReader.ts b/frontend/app/player/web/messages/MStreamReader.ts index a37e43c46..4b08aae9e 100644 --- a/frontend/app/player/web/messages/MStreamReader.ts +++ b/frontend/app/player/web/messages/MStreamReader.ts @@ -12,7 +12,7 @@ export default class MStreamReader { private t: number = 0 private idx: number = 0 - readNext(): Message | null { + readNext(): Message & { _index: number } | null { let msg = this.r.readMessage() if (msg === null) { return null } if (msg.tp === MType.Timestamp) { From fcee35ee3ed43289d88cf6c3ecf6bbafa4f80d61 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 27 Dec 2022 08:15:46 +0100 Subject: [PATCH 13/25] fix(ui) - active session modal list compact --- .../components/Assist/components/SessionList/SessionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index 6de55c0d1..ad4eb4df5 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -56,7 +56,7 @@ function SessionList(props: Props) { {session.pageTitle} )} - hideModal()} key={session.sessionId} session={session} /> + hideModal()} key={session.sessionId} session={session} /> ))} From 0fe5d3c8d34a38d8368e92618aad31ec122b63ca Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 27 Dec 2022 10:28:27 +0100 Subject: [PATCH 14/25] feat(tracker):reset batchSize to initial after sending a big message --- tracker/tracker/src/webworker/BatchWriter.ts | 25 ++++++++++--------- .../tracker/src/webworker/PrimitiveEncoder.ts | 2 +- tracker/tracker/src/webworker/index.ts | 5 ++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index 47ef6560c..167c1aff6 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -36,7 +36,7 @@ export default class BatchWriter { } private prepare(): void { - if (!this.encoder.isEmpty()) { + if (!this.encoder.isEmpty) { return } @@ -94,19 +94,20 @@ export default class BatchWriter { if (this.writeWithSize(message)) { return } + // buffer overflow, send already written data first then try again this.finaliseBatch() - while (!this.writeWithSize(message)) { - if (this.beaconSize === this.beaconSizeLimit) { - console.warn('OpenReplay: beacon size overflow. Skipping large message.', message, this) - this.encoder.reset() - this.prepare() - return - } - // MBTODO: tempWriter for one message? - this.beaconSize = Math.min(this.beaconSize * 2, this.beaconSizeLimit) - this.encoder = new MessageEncoder(this.beaconSize) - this.prepare() + if (this.writeWithSize(message)) { + return } + // buffer is too small. Create one with maximal capacity + this.encoder = new MessageEncoder(this.beaconSizeLimit) + this.prepare() + if (!this.writeWithSize(message)) { + console.warn('OpenReplay: beacon size overflow. Skipping large message.', message, this) + } + // reset encoder to normal size + this.encoder = new MessageEncoder(this.beaconSize) + this.prepare() } finaliseBatch() { diff --git a/tracker/tracker/src/webworker/PrimitiveEncoder.ts b/tracker/tracker/src/webworker/PrimitiveEncoder.ts index 34cd3f055..ac3e98a03 100644 --- a/tracker/tracker/src/webworker/PrimitiveEncoder.ts +++ b/tracker/tracker/src/webworker/PrimitiveEncoder.ts @@ -66,7 +66,7 @@ export default class PrimitiveEncoder { checkpoint() { this.checkpointOffset = this.offset } - isEmpty(): boolean { + get isEmpty(): boolean { return this.offset === 0 } skip(n: number): boolean { diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 6cddb8a6b..2c6253210 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -1,3 +1,8 @@ +// Do strong type WebWorker as soon as it is possible: +// https://github.com/microsoft/TypeScript/issues/14877 +// At the moment "webworker" lib conflicts with jest-environment-jsdom that uses "dom" lib +// + import type Message from '../common/messages.gen.js' import { Type as MType } from '../common/messages.gen.js' import { ToWorkerData, FromWorkerData } from '../common/interaction.js' From 63857a2f01f522ce56616474c16eaa550c3857fb Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 27 Dec 2022 16:41:00 +0100 Subject: [PATCH 15/25] change(ui): remove old reducers, rewrite eventsblock component --- .../Alerts/Notifications/Notifications.tsx | 26 +- .../CustomMetricWidget/CustomMetricWidget.tsx | 2 - .../components/FilterSeries/FilterSeries.tsx | 50 ++-- .../app/components/Session/RightBlock.tsx | 37 ++- .../components/Session_/EventsBlock/Event.js | 8 - .../EventsBlock/EventSearch/EventSearch.js | 8 +- .../Session_/EventsBlock/EventsBlock.js | 249 ------------------ .../Session_/EventsBlock/EventsBlock.tsx | 187 +++++++++++++ .../PageInsightsPanel/PageInsightsPanel.tsx | 1 - .../FilterSeries/FilterSeries.tsx | 112 -------- .../FilterSeries/SeriesName/SeriesName.tsx | 57 ---- .../FilterSeries/SeriesName/index.ts | 1 - .../CustomMetrics/FilterSeries/index.ts | 1 - .../shared/DevTools/useCellMeasurerCache.ts | 10 +- frontend/app/duck/ReducerModule.js.dev | 55 ---- frontend/app/duck/customMetrics.js | 43 +-- frontend/app/duck/environments.js | 7 - frontend/app/duck/events.js | 81 ------ frontend/app/duck/index.ts | 9 +- frontend/app/duck/notifications.js | 63 ----- frontend/app/duck/sessions.js | 16 +- frontend/app/duck/user.js | 1 - frontend/app/duck/variables.js | 9 - frontend/app/mstore/userStore.ts | 4 +- frontend/app/types/address.js | 20 -- frontend/app/types/appTest.js | 87 ------ frontend/app/types/member.ts | 18 +- 27 files changed, 265 insertions(+), 897 deletions(-) delete mode 100644 frontend/app/components/Session_/EventsBlock/EventsBlock.js create mode 100644 frontend/app/components/Session_/EventsBlock/EventsBlock.tsx delete mode 100644 frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx delete mode 100644 frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx delete mode 100644 frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts delete mode 100644 frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts delete mode 100644 frontend/app/duck/ReducerModule.js.dev delete mode 100644 frontend/app/duck/environments.js delete mode 100644 frontend/app/duck/events.js delete mode 100644 frontend/app/duck/notifications.js delete mode 100644 frontend/app/duck/variables.js delete mode 100644 frontend/app/types/address.js delete mode 100644 frontend/app/types/appTest.js diff --git a/frontend/app/components/Alerts/Notifications/Notifications.tsx b/frontend/app/components/Alerts/Notifications/Notifications.tsx index 05bcea06b..2bc78384f 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.tsx +++ b/frontend/app/components/Alerts/Notifications/Notifications.tsx @@ -1,34 +1,27 @@ import React, { useEffect } from 'react'; import stl from './notifications.module.css'; -import { connect } from 'react-redux'; import { Icon, Tooltip } from 'UI'; -import { fetchList, setViewed, clearAll } from 'Duck/notifications'; -import { setLastRead } from 'Duck/announcements'; import { useModal } from 'App/components/Modal'; import AlertTriggersModal from 'Shared/AlertTriggersModal'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; const AUTOREFRESH_INTERVAL = 5 * 60 * 1000; -interface Props { - notifications: any; - fetchList: any; -} -function Notifications(props: Props) { +function Notifications() { const { showModal } = useModal(); const { notificationStore } = useStore(); - const count = useObserver(() => notificationStore.notificationsCount); + const count = notificationStore.notificationsCount; useEffect(() => { const interval = setInterval(() => { - notificationStore.fetchNotificationsCount(); + void notificationStore.fetchNotificationsCount(); }, AUTOREFRESH_INTERVAL); return () => clearInterval(interval); }, []); - return useObserver(() => ( + return (
- )); + ); } -export default connect( - (state: any) => ({ - notifications: state.getIn(['notifications', 'list']), - }), - { fetchList, setLastRead, setViewed, clearAll } -)(Notifications); +export default observer(Notifications) \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index b804ca439..71820c502 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -9,7 +9,6 @@ import { init, edit, remove, - setAlertMetricId, setActiveWidget, updateActiveState, } from 'Duck/customMetrics'; @@ -181,7 +180,6 @@ export default connect( { remove, setShowAlerts, - setAlertMetricId, edit, setActiveWidget, updateActiveState, diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index ccb269cbd..bcfd27406 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -1,14 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import FilterList from 'Shared/Filters/FilterList'; -import { - edit, - updateSeries, - addSeriesFilterFilter, - removeSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, -} from 'Duck/customMetrics'; -import { connect } from 'react-redux'; import { Button, Icon } from 'UI'; import FilterSelection from 'Shared/Filters/FilterSelection'; import SeriesName from './SeriesName'; @@ -18,21 +9,21 @@ import { observer } from 'mobx-react-lite'; interface Props { seriesIndex: number; series: any; - edit: typeof edit; - updateSeries: typeof updateSeries; onRemoveSeries: (seriesIndex: any) => void; - canDelete?: boolean; - addSeriesFilterFilter: typeof addSeriesFilterFilter; - editSeriesFilterFilter: typeof editSeriesFilterFilter; - editSeriesFilter: typeof editSeriesFilter; - removeSeriesFilterFilter: typeof removeSeriesFilterFilter; + canDelete?: boolean; hideHeader?: boolean; emptyMessage?: any; observeChanges?: () => void; } function FilterSeries(props: Props) { - const { observeChanges = () => {}, canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; + const { + observeChanges = () => { + }, + canDelete, + hideHeader = false, + emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' + } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -46,7 +37,7 @@ function FilterSeries(props: Props) { observeChanges() } - const onChangeEventsOrder = (e, { name, value }: any) => { + const onChangeEventsOrder = (e: any, { name, value }: any) => { series.filter.updateKey(name, value) observeChanges() } @@ -60,11 +51,11 @@ function FilterSeries(props: Props) {
- series.update('name', name) } /> + series.update('name', name)} />
- +
-
+
@@ -73,17 +64,17 @@ function FilterSeries(props: Props) {
- { expanded && ( + {expanded && ( <>
- { series.filter.filters.length > 0 ? ( + {series.filter.filters.length > 0 ? ( - ): ( + ) : (
{emptyMessage}
)}
@@ -103,11 +94,4 @@ function FilterSeries(props: Props) { ); } -export default connect(null, { - edit, - updateSeries, - addSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, - removeSeriesFilterFilter, -})(observer(FilterSeries)); \ No newline at end of file +export default observer(FilterSeries); \ No newline at end of file diff --git a/frontend/app/components/Session/RightBlock.tsx b/frontend/app/components/Session/RightBlock.tsx index 66f92a93c..9e4302648 100644 --- a/frontend/app/components/Session/RightBlock.tsx +++ b/frontend/app/components/Session/RightBlock.tsx @@ -1,33 +1,30 @@ import React from 'react' import EventsBlock from '../Session_/EventsBlock'; import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel' -import { PlayerContext } from 'App/components/Session/playerContext'; -import { observer } from 'mobx-react-lite'; import cn from 'classnames'; import stl from './rightblock.module.css'; function RightBlock(props: any) { const { activeTab } = props; - const { player, store } = React.useContext(PlayerContext) - const { eventListNow, playing } = store.get() - const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 - - const EventsBlockConnected = () => - const renderActiveTab = (tab: string) => { - switch(tab) { - case props.tabs.EVENTS: - return - case props.tabs.HEATMAPS: - return - } + if (activeTab === props.tabs.EVENTS) { + return ( +
+ +
+ ) } - return ( -
- {renderActiveTab(activeTab)} -
- ) + if (activeTab === props.tabs.HEATMAPS) { + return ( +
+ +
+ ) + } + return null } -export default observer(RightBlock) +export default RightBlock diff --git a/frontend/app/components/Session_/EventsBlock/Event.js b/frontend/app/components/Session_/EventsBlock/Event.js index bfae9fe5d..e8f985aa0 100644 --- a/frontend/app/components/Session_/EventsBlock/Event.js +++ b/frontend/app/components/Session_/EventsBlock/Event.js @@ -147,14 +147,6 @@ export default class Event extends React.PureComponent { }
- {/*
- -
*/}
{ this.renderBody() }
diff --git a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js index 3c3396e72..965e9726d 100644 --- a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js +++ b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js @@ -5,16 +5,10 @@ import { PlayerContext } from 'App/components/Session/playerContext'; function EventSearch(props) { const { player } = React.useContext(PlayerContext) - const { onChange, clearSearch, value, header, setActiveTab } = props; + const { onChange, value, header, setActiveTab } = props; const toggleEvents = () => player.toggleEvents() - useEffect(() => { - return () => { - clearSearch() - } - }, []) - return (
diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js deleted file mode 100644 index b3d978347..000000000 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ /dev/null @@ -1,249 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from "react-virtualized"; -import { TYPES } from 'Types/session/event'; -import { setSelected } from 'Duck/events'; -import { setEventFilter, filterOutNote } from 'Duck/sessions'; -import { show as showTargetDefiner } from 'Duck/components/targetDefiner'; -import EventGroupWrapper from './EventGroupWrapper'; -import styles from './eventsBlock.module.css'; -import EventSearch from './EventSearch/EventSearch'; - -@connect(state => ({ - session: state.getIn([ 'sessions', 'current' ]), - filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), - eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), - selectedEvents: state.getIn([ 'events', 'selected' ]), - targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]), - testsAvaliable: false, -}), { - showTargetDefiner, - setSelected, - setEventFilter, - filterOutNote -}) -export default class EventsBlock extends React.Component { - state = { - editingEvent: null, - mouseOver: false, - query: '' - } - - scroller = React.createRef(); - cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 300 - }); - - write = ({ target: { value, name } }) => { - const { filter } = this.state; - this.setState({ query: value }) - this.props.setEventFilter({ query: value, filter }) - - setTimeout(() => { - if (!this.scroller.current) return; - - this.scroller.current.scrollToRow(0); - }, 100) - } - - clearSearch = () => { - const { filter } = this.state; - this.setState({ query: '' }) - this.props.setEventFilter({ query: '', filter }) - if (this.scroller.current) { - this.scroller.current.forceUpdateGrid(); - } - - setTimeout(() => { - if (!this.scroller.current) return; - - this.scroller.current.scrollToRow(0); - }, 100) - } - - onSetEventFilter = (e, { name, value }) => { - const { query } = this.state; - this.setState({ filter: value }) - this.props.setEventFilter({ filter: value, query }); - } - - componentDidUpdate(prevProps) { - if (prevProps.targetDefinerDisplayed && !this.props.targetDefinerDisplayed) { - this.setState({ editingEvent: null }); - } - if (prevProps.session !== this.props.session) { // Doesn't happen - this.cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 300 - }); - } - if (prevProps.currentTimeEventIndex !== this.props.currentTimeEventIndex && - this.scroller.current !== null) { - this.scroller.current.forceUpdateGrid(); - if (!this.state.mouseOver) { - this.scroller.current.scrollToRow(this.props.currentTimeEventIndex); - } - } - } - - onCheckboxClick(e, event) { - e.stopPropagation(); - const { - session: { events }, - selectedEvents, - } = this.props; - - this.props.player.pause(); - - let newSelectedSet; - const wasSelected = selectedEvents.contains(event); - if (wasSelected) { - newSelectedSet = selectedEvents.remove(event); - } else { - newSelectedSet = selectedEvents.add(event); - } - - let selectNextLoad = false; - events.reverse().forEach((sessEvent) => { - if (sessEvent.type === TYPES.LOCATION) { - if (selectNextLoad) { - newSelectedSet = newSelectedSet.add(sessEvent); - } - selectNextLoad = false; - } else if (newSelectedSet.contains(sessEvent)) { - selectNextLoad = true; - } - }); - this.props.setSelected(newSelectedSet); - } - - onEventClick = (e, event) => this.props.player.jump(event.time) - - onMouseOver = () => this.setState({ mouseOver: true }) - onMouseLeave = () => this.setState({ mouseOver: false }) - - get eventsList() { - const { session: { notesWithEvents }, filteredEvents } = this.props - const usedEvents = filteredEvents || notesWithEvents - - return usedEvents - } - - renderGroup = ({ index, key, style, parent }) => { - const { - selectedEvents, - currentTimeEventIndex, - testsAvaliable, - playing, - eventsIndex, - filterOutNote, - } = this.props; - - const { query } = this.state; - const _events = this.eventsList - const isLastEvent = index === _events.size - 1; - const isLastInGroup = isLastEvent || _events.get(index + 1).type === TYPES.LOCATION; - const event = _events.get(index); - const isNote = !!event.noteId - const isSelected = selectedEvents.includes(event); - const isCurrent = index === currentTimeEventIndex; - const isEditing = this.state.editingEvent === event; - - const heightBug = index === 0 && event.type === TYPES.LOCATION && event.referrer ? { top: 2 } : {} - return ( - - {({measure, registerChild}) => ( -
- -
- )} -
- ); - } - - render() { - const { query } = this.state; - const { - session: { - events, - }, - setActiveTab, - } = this.props; - - const _events = this.eventsList - - const isEmptySearch = query && (_events.size === 0 || !_events) - return ( - <> -
-
- User Steps { events.size }
- } - /> -
-
-
- {isEmptySearch && ( -
- - No Matching Results -
- )} - - {({ height }) => ( - - )} - -
- - ); - } -} diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx new file mode 100644 index 000000000..0f0405cd5 --- /dev/null +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { List, AutoSizer, CellMeasurer } from "react-virtualized"; +import { TYPES } from 'Types/session/event'; +import { setEventFilter, filterOutNote } from 'Duck/sessions'; +import { show as showTargetDefiner } from 'Duck/components/targetDefiner'; +import EventGroupWrapper from './EventGroupWrapper'; +import styles from './eventsBlock.module.css'; +import EventSearch from './EventSearch/EventSearch'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { RootStore } from 'App/duck' +import { List as ImmList } from 'immutable' +import useCellMeasurerCache from 'Components/shared/DevTools/useCellMeasurerCache' + +interface IProps { + setEventFilter: (filter: { query: string }) => void + filteredEvents: ImmList> + setActiveTab: (tab?: string) => void + query: string + session: Record + filterOutNote: (id: string) => void + eventsIndex: number[] +} + +function EventsBlock(props: IProps) { + const [mouseOver, setMouseOver] = React.useState(true) + const scroller = React.useRef(null) + const cache = useCellMeasurerCache(undefined, { + fixedWidth: true, + defaultHeight: 300 + }); + + const { store, player } = React.useContext(PlayerContext) + + const { eventListNow, playing } = store.get() + + const { session: { events, notesWithEvents }, filteredEvents, + eventsIndex, + filterOutNote, + query, + setActiveTab, + } = props + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 + const usedEvents = filteredEvents || notesWithEvents + + const write = ({ target: { value } }: React.ChangeEvent) => { + props.setEventFilter({ query: value }) + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + const clearSearch = () => { + props.setEventFilter({ query: '' }) + if (scroller.current) { + scroller.current.forceUpdateGrid(); + } + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + React.useEffect(() => { + return () => { + clearSearch() + } + }, []) + React.useEffect(() => { + if (scroller.current) { + scroller.current.forceUpdateGrid(); + if (!mouseOver) { + scroller.current.scrollToRow(currentTimeEventIndex); + } + } + }, [currentTimeEventIndex]) + + const onEventClick = (_: React.MouseEvent, event: any) => player.jump(event.time) + const onMouseOver = () => setMouseOver(true) + const onMouseLeave = () => setMouseOver(false) + + const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: any; parent: any }) => { + const isLastEvent = index === usedEvents.size - 1; + const isLastInGroup = isLastEvent || usedEvents.get(index + 1)?.type === TYPES.LOCATION; + const event = usedEvents.get(index); + const isNote = !!event?.noteId + const isCurrent = index === currentTimeEventIndex; + + const heightBug = index === 0 && event?.type === TYPES.LOCATION && event.referrer ? { top: 2 } : {} + return ( + + {({measure, registerChild}) => ( +
+ +
+ )} +
+ ); + } + + const isEmptySearch = query && (usedEvents.size === 0 || !usedEvents) + return ( + <> +
+
+ User Steps { events.size }
+ } + /> +
+
+
+ {isEmptySearch && ( +
+ + No Matching Results +
+ )} + + {({ height }) => ( + + )} + +
+ + ); +} + +export default connect((state: RootStore) => ({ + session: state.getIn([ 'sessions', 'current' ]), + filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), + query: state.getIn(['sessions', 'eventsQuery']), + eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), + targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]), +}), { + showTargetDefiner, + setEventFilter, + filterOutNote +})(observer(EventsBlock)) diff --git a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx index e1ba5ccba..b43f70ed9 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx @@ -13,7 +13,6 @@ const JUMP_OFFSET = 1000; interface Props { filters: any; fetchInsights: (filters: Record) => void; - urls: []; insights: any; events: Array; urlOptions: Array; diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx deleted file mode 100644 index 8f8aca480..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState } from 'react'; -import FilterList from 'Shared/Filters/FilterList'; -import { - edit, - updateSeries, - addSeriesFilterFilter, - removeSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, -} from 'Duck/customMetrics'; -import { connect } from 'react-redux'; -import { IconButton, Icon } from 'UI'; -import FilterSelection from '../../Filters/FilterSelection'; -import SeriesName from './SeriesName'; -import cn from 'classnames'; - -interface Props { - seriesIndex: number; - series: any; - edit: typeof edit; - updateSeries: typeof updateSeries; - onRemoveSeries: (seriesIndex) => void; - canDelete?: boolean; - addSeriesFilterFilter: typeof addSeriesFilterFilter; - editSeriesFilterFilter: typeof editSeriesFilterFilter; - editSeriesFilter: typeof editSeriesFilter; - removeSeriesFilterFilter: typeof removeSeriesFilterFilter; - hideHeader?: boolean; - emptyMessage?: any; -} - -function FilterSeries(props: Props) { - const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; - const [expanded, setExpanded] = useState(true) - const { series, seriesIndex } = props; - - const onAddFilter = (filter) => { - filter.value = [""] - if (filter.hasOwnProperty('filters') && Array.isArray(filter.filters)) { - filter.filters = filter.filters.map(i => ({ ...i, value: [""] })) - } - props.addSeriesFilterFilter(seriesIndex, filter); - } - - const onUpdateFilter = (filterIndex, filter) => { - props.editSeriesFilterFilter(seriesIndex, filterIndex, filter); - } - - const onChangeEventsOrder = (e, { name, value }) => { - props.editSeriesFilter(seriesIndex, { eventsOrder: value }); - } - - const onRemoveFilter = (filterIndex) => { - props.removeSeriesFilterFilter(seriesIndex, filterIndex); - } - - return ( -
-
-
- props.updateSeries(seriesIndex, { name }) } /> -
- -
-
- -
- -
setExpanded(!expanded)} className="ml-3"> - -
- -
-
- { expanded && ( - <> -
- { series.filter.filters.size > 0 ? ( - - ): ( -
{emptyMessage}
- )} -
-
-
- - - -
-
- - )} -
- ); -} - -export default connect(null, { - edit, - updateSeries, - addSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, - removeSeriesFilterFilter, -})(FilterSeries); \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx deleted file mode 100644 index d6a69c73d..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Icon } from 'UI'; - -interface Props { - name: string; - onUpdate: (name) => void; - seriesIndex?: number; -} -function SeriesName(props: Props) { - const { seriesIndex = 1 } = props; - const [editing, setEditing] = useState(false) - const [name, setName] = useState(props.name) - const ref = useRef(null) - - const write = ({ target: { value, name } }) => { - setName(value) - } - - const onBlur = () => { - setEditing(false) - props.onUpdate(name) - } - - useEffect(() => { - if (editing) { - ref.current.focus() - } - }, [editing]) - - useEffect(() => { - setName(props.name) - }, [props.name]) - - // const { name } = props; - return ( -
- { editing ? ( - setEditing(true)} - /> - ) : ( -
{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }
- )} - -
setEditing(true)}>
-
- ); -} - -export default SeriesName; \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts deleted file mode 100644 index 90e63cdb6..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SeriesName'; \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts deleted file mode 100644 index 5882e382a..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FilterSeries' \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/useCellMeasurerCache.ts b/frontend/app/components/shared/DevTools/useCellMeasurerCache.ts index 2c94f7856..692f2629f 100644 --- a/frontend/app/components/shared/DevTools/useCellMeasurerCache.ts +++ b/frontend/app/components/shared/DevTools/useCellMeasurerCache.ts @@ -1,12 +1,12 @@ import { useMemo } from 'react' -import { CellMeasurerCache } from 'react-virtualized'; +import { CellMeasurerCache, CellMeasurerCacheParams } from 'react-virtualized'; import useLatestRef from 'App/hooks/useLatestRef' - -export default function useCellMeasurerCache(itemList: any[]) { - const filteredListRef = useLatestRef(itemList) +export default function useCellMeasurerCache(itemList?: any[], options?: CellMeasurerCacheParams) { + const filteredListRef = itemList ? useLatestRef(itemList) : undefined return useMemo(() => new CellMeasurerCache({ fixedWidth: true, - keyMapper: (index) => filteredListRef.current[index], + keyMapper: filteredListRef ? (index) => filteredListRef.current[index] : undefined, + ...options }), []) } \ No newline at end of file diff --git a/frontend/app/duck/ReducerModule.js.dev b/frontend/app/duck/ReducerModule.js.dev deleted file mode 100644 index c80c65a37..000000000 --- a/frontend/app/duck/ReducerModule.js.dev +++ /dev/null @@ -1,55 +0,0 @@ - -redux -> other storage ::<< Entities + Lists + relations <|> methods:: crud. request declaration -> request realisation with middleware -< (uses) MODEL - - - -!request declaration - - - -action/request formatter => ReducerModule Fabrique => - - -class ReducerModule { - _ns = "common" - _switch = {} - _n = 0 - - constructor(namespace) { - this._ns = namespace - } - - /** - Action: state => newState | { reduce: state, action => newState, creator: () => {objects to action} } - */ - actions(actns): this { - Object.keys(actns).map(key => { - const type = `${this._namespace}/${key.toUpperCase()}`; - this._switch[ type ] = actns[ key ]; - }); - return this; - } - - requests(reqsts): this { - Object.keys(reqsts).map(key => { - const type = `${this._namespace}/${key.toUpperCase()}`; - this._switch[ type ] = actns[ key ]; - }); - return this; - } - - get actionTypes() { - - } - - get actionCreators() { - - } - - get reducer() { - return (state, action = {}) => { - const reduce = this._switch[ action.type ]; - return reduce ? reduce(state, action) : state; - } - } -} \ No newline at end of file diff --git a/frontend/app/duck/customMetrics.js b/frontend/app/duck/customMetrics.js index e6713acb4..82ce78277 100644 --- a/frontend/app/duck/customMetrics.js +++ b/frontend/app/duck/customMetrics.js @@ -1,8 +1,8 @@ import { List, Map } from 'immutable'; import CustomMetric, { FilterSeries } from 'Types/customMetric' -import { createFetch, fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud'; +import { fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, createListUpdater, mergeReducers } from './funcTools/tools'; import Filter from 'Types/filter'; import Session from 'Types/session'; @@ -28,11 +28,6 @@ const INIT = `${name}/INIT`; const SET_ACTIVE_WIDGET = `${name}/SET_ACTIVE_WIDGET`; const REMOVE = removeType(name); const UPDATE_SERIES = `${name}/UPDATE_SERIES`; -const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`; - -function chartWrapper(chart = []) { - return chart.map(point => ({ ...point, count: Math.max(point.count, 0) })); -} const updateItemInList = createListUpdater(idKey); const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ] @@ -117,12 +112,6 @@ export default mergeReducers( export const edit = createEdit(name); export const remove = createRemove(name); -export const updateSeries = (index, series) => ({ - type: UPDATE_SERIES, - index, - series, -}); - export function fetch(id) { return { id, @@ -147,34 +136,6 @@ export function fetchList() { }; } -export function setAlertMetricId(id) { - return { - type: SET_ALERT_METRIC_ID, - id, - }; -} - -export const addSeries = (series = null) => (dispatch, getState) => { - const instance = getState().getIn([ 'customMetrics', 'instance' ]); - const seriesIndex = instance.series.size; - const newSeries = series || { - name: `Series ${seriesIndex + 1}`, - filter: new Filter({ filters: [], eventsOrder: 'then' }), - }; - - dispatch({ - type: ADD_SERIES, - series: newSeries, - }); -} - -export const removeSeries = (index) => (dispatch, getState) => { - dispatch({ - type: REMOVE_SERIES, - index, - }); -} - export const init = (instance = null, forceNull = false) => (dispatch, getState) => { dispatch({ type: INIT, diff --git a/frontend/app/duck/environments.js b/frontend/app/duck/environments.js deleted file mode 100644 index 8356c4281..000000000 --- a/frontend/app/duck/environments.js +++ /dev/null @@ -1,7 +0,0 @@ -import Environment from 'Types/environment'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('environment', Environment); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/duck/events.js b/frontend/app/duck/events.js deleted file mode 100644 index 000bd24b7..000000000 --- a/frontend/app/duck/events.js +++ /dev/null @@ -1,81 +0,0 @@ -import { List, Map, Set } from 'immutable'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; -import Event from 'Types/filter/event'; -import CustomFilter from 'Types/filter/customFilter'; -import { KEYS } from 'Types/filter/customFilter'; -import logger from 'App/logger'; -import { countries } from 'App/constants'; -import { getRE } from 'App/utils'; - -const FETCH_LIST = new RequestTypes('events/FETCH_LIST'); -const TOGGLE_SELECT = 'events/TOGGLE_SELECT'; -const SET_SELECTED = 'events/SET_SELECTED'; - -const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true })); - -const initialState = Map({ - list: List(), - store: Set(), - - // replace? - selected: Set(), -}); - -const filterKeys = ['METADATA', KEYS.USERID, KEYS.USER_COUNTRY, KEYS.USER_BROWSER, KEYS.USER_OS, KEYS.USER_DEVICE, KEYS.REFERRER] - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_LIST.SUCCESS: { - const regCountry = getRE(action.params.q, 'i'); - const countryOptionsFiltered = List(countryOptions).filter(({ actualValue }) => regCountry.test(actualValue)).take(5); - - const eventList = List(action.data).concat(countryOptionsFiltered).map(item => ( - filterKeys.includes(item.type) ? - CustomFilter({...item, isFilter: true }) : - Event({...item, key: item.type, filterKey: item.type, label: item.type}) ) - ); - - return state - .set('list', eventList) - .update('store', store => store.concat(eventList)); - } - // TODO: use ids. or make a set-hoc? - case TOGGLE_SELECT: { - const { event, flag } = action; - const shouldBeInSet = typeof flag === 'boolean' - ? flag - : !state.get('selected').contains(event); - return state.update('selected', set => (shouldBeInSet - ? set.add(event) - : set.remove(event))); - } - case SET_SELECTED: - return state.set('selected', Set(action.events)); - } - return state; -}; - -export default withRequestState(FETCH_LIST, reducer); - -export function fetchList(params) { - return { - types: FETCH_LIST.toArray(), - call: client => client.get('/events/search', params), - params, - }; -} - -export function toggleSelect(event, flag) { - return { - type: TOGGLE_SELECT, - event, - flag, - }; -} - -export function setSelected(events) { - return { - type: SET_SELECTED, - events, - }; -} diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts index fd4b0a655..d33de09a0 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -1,3 +1,4 @@ +// @ts-ignore import { combineReducers } from 'redux-immutable'; import jwt from './jwt'; @@ -8,12 +9,8 @@ import target from './target'; import targetCustom from './targetCustom'; import filters from './filters'; import funnelFilters from './funnelFilters'; -import events from './events'; -import environments from './environments'; -import variables from './variables'; import templates from './templates'; import alerts from './alerts'; -import notifications from './notifications'; import dashboard from './dashboard'; import components from './components'; import sources from './sources'; @@ -43,12 +40,8 @@ const rootReducer = combineReducers({ filters, funnelFilters, - events, - environments, - variables, templates, alerts, - notifications, dashboard, components, members, diff --git a/frontend/app/duck/notifications.js b/frontend/app/duck/notifications.js deleted file mode 100644 index 129349792..000000000 --- a/frontend/app/duck/notifications.js +++ /dev/null @@ -1,63 +0,0 @@ -import { List, Map } from 'immutable'; -import Notification from 'Types/notification'; -import { mergeReducers, success, array, request, createListUpdater } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList, -} from './funcTools/crud'; - -const name = 'notification'; -const idKey = 'notificationId'; -const SET_VIEWED = 'notifications/SET_VIEWED'; -const CLEAR_ALL = 'notifications/CLEAR_ALL'; -const SET_VIEWED_SUCCESS = success(SET_VIEWED); -const CLEAR_ALL_SUCCESS = success(CLEAR_ALL); - -const listUpdater = createListUpdater(idKey); - -const initialState = Map({ - list: List(), -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_VIEWED_SUCCESS: - if (!action.data) return state; - const item = state.get('list').find(item => item[ idKey ] === action.id) - return listUpdater(state, Notification({...item.toJS(), createdAt: item.createdAt.ts, viewed: true })); - case CLEAR_ALL_SUCCESS: - if (!action.data) return state; - return state.update('list', list => list.map(l => Notification({...l.toJS(), createdAt: l.createdAt.ts, viewed: true }))) - } - return state; -}; - -export const fetchList = createFetchList(name); - -export default mergeReducers( - reducer, - createCRUDReducer(name, Notification, idKey), - createRequestReducer({ - setViewed: SET_VIEWED, - clearAll: CLEAR_ALL, - ...getCRUDRequestTypes(name), - }), -); - -export function setViewed(id) { - return { - types: array(SET_VIEWED), - call: client => client.get(`/notifications/${ id }/view`), - id, - }; -} - -export function clearAll(params) { - return { - types: array(CLEAR_ALL), - call: client => client.post('/notifications/view', params), - }; -} - diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index 8afb6d073..b78d8fad4 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -60,6 +60,7 @@ const initialState = Map({ eventsIndex: [], sourcemapUploaded: true, filteredEvents: null, + eventsQuery: '', showChatWindow: false, liveSessions: List(), visitedEvents: List(), @@ -110,14 +111,17 @@ const reducer = (state = initialState, action = {}) => { case SET_EVENT_QUERY: { const events = state.get('current').events; const query = action.filter.query; - // const filter = action.filter.filter; const searchRe = getRE(query, 'i'); - let filteredEvents = query ? events.filter((e) => searchRe.test(e.url) || searchRe.test(e.value) || searchRe.test(e.label)) : null; - // if (filter) { - // filteredEvents = filteredEvents ? filteredEvents.filter(e => e.type === filter) : events.filter(e => e.type === filter); - // } - return state.set('filteredEvents', filteredEvents); + const filteredEvents = query ? events.filter( + (e) => searchRe.test(e.url) + || searchRe.test(e.value) + || searchRe.test(e.label) + || searchRe.test(e.type) + || (e.type === 'LOCATION' && searchRe.test('visited')) + ) : null; + + return state.set('filteredEvents', filteredEvents).set('eventsQuery', query); } case FETCH.SUCCESS: { // TODO: more common.. or TEMP diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index 5b39d34db..f97283290 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -56,7 +56,6 @@ const reducer = (state = initialState, action = {}) => { return state.set('passwordErrors', List(action.errors)) case DELETE: deleteCookie('jwt', '/', '.openreplay.com') - console.log('called') return initialState; case PUT_CLIENT.REQUEST: return state.mergeIn([ 'account' ], action.params); diff --git a/frontend/app/duck/variables.js b/frontend/app/duck/variables.js deleted file mode 100644 index 21a0131c4..000000000 --- a/frontend/app/duck/variables.js +++ /dev/null @@ -1,9 +0,0 @@ -import Variable from 'Types/variable'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('variable', Variable); -export const { - fetchList, fetch, init, edit, save, remove, -} = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts index 8e8913de6..b46f776a3 100644 --- a/frontend/app/mstore/userStore.ts +++ b/frontend/app/mstore/userStore.ts @@ -44,7 +44,7 @@ export default class UserStore { } initUser(user?: any ): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (user) { this.instance = new User().fromJson(user.toJson()); } else { @@ -54,7 +54,7 @@ export default class UserStore { }) } - updateKey(key: string, value: any) { + updateKey(key: keyof this, value: any) { this[key] = value if (key === 'searchQuery') { diff --git a/frontend/app/types/address.js b/frontend/app/types/address.js deleted file mode 100644 index 8d8e2ccf2..000000000 --- a/frontend/app/types/address.js +++ /dev/null @@ -1,20 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - line1: '', - postal_code: '', - city: '', - state: '', - country: '', -}, { - methods: { - validate() { - return true; - }, - toData() { - const js = this.toJS(); - delete js.key; - return js; - }, - }, -}); diff --git a/frontend/app/types/appTest.js b/frontend/app/types/appTest.js deleted file mode 100644 index 248b759b7..000000000 --- a/frontend/app/types/appTest.js +++ /dev/null @@ -1,87 +0,0 @@ -import { Record, List, Set } from 'immutable'; -import { validateName } from 'App/validate'; -import { DateTime } from 'luxon'; -import Run from './run'; -import Step from './step'; - -class Test extends Record({ - testId: undefined, - name: 'Unnamed Test', - steps: List(), - stepsCount: undefined, - framework: 'selenium', - sessionId: undefined, - generated: false, - tags: Set(), - runHistory: List(), - editedAt: undefined, - seqId: undefined, - seqChange: false, - uptime: 0, -}) { - // ???TODO - // idKey = "testId" - - exists() { - return this.testId !== undefined; - } - - validate() { - if (this.steps.size === 0) return false; - - return validateName(this.name, { - empty: false, - admissibleChars: ':-', - }); - } - - isComplete() { - return this.stepsCount === this.steps.size; - } - - // not the best code - toData() { - const js = this - .update('steps', steps => steps.map(step => step.toData())) - .toJS(); - - if (js.seqChange) { - const { testId, seqId } = js; - return { testId, seqId }; - } - - delete js.stepsCount; - delete js.seqChange; - - return js; - } - // not the best code -} - -const fromJS = (test = {}) => { - if (test instanceof Test) return test; - - const stepsLength = test.steps && test.steps.length; // - const editedAt = test.editedAt ? DateTime.fromMillis(test.editedAt) : undefined; - - const lastRun = Run(test.lastRun); - const runHistory = List(test.runHistory) // TODO: GOOD ENDPOINTS - .map(run => { - if (typeof run === 'string') { - return run === lastRun.runId - ? lastRun - : Run({ runId: run }) - } - return Run(run); - }); - - return new Test({ ...test, editedAt, uptime: parseInt(test.passed / test.count * 100) || 0 }) - .set('stepsCount', typeof test.stepsCount === 'number' - ? test.stepsCount - : stepsLength) // - .set('runHistory', runHistory) - .set('steps', List(test.steps).map(Step)) - .set('tags', test.tags && Set(test.tags.map(t => t.toLowerCase()))); -}; - -export default fromJS; diff --git a/frontend/app/types/member.ts b/frontend/app/types/member.ts index d3914eac9..0a88d51ba 100644 --- a/frontend/app/types/member.ts +++ b/frontend/app/types/member.ts @@ -16,6 +16,20 @@ export interface IMember { invitationLink: string } +export interface IMemberApiRes { + userId: string + name: string + email: string + createdAt: string + admin: boolean + superAdmin: boolean + joined: boolean + expiredInvitation: boolean + roleId: string + roleName: string + invitationLink: string +} + export default Record({ id: undefined, name: '', @@ -42,9 +56,9 @@ export default Record({ return js; }, }, - fromJS: ({ createdAt, ...rest }) => ({ + fromJS: ({ createdAt, ...rest }: IMemberApiRes) => ({ ...rest, - createdAt: createdAt && DateTime.fromISO(createdAt || 0), + createdAt: createdAt && DateTime.fromISO(createdAt || '0'), id: rest.userId, }), }); From 8e6eb8ac1bb2251598288fa955bb22e832c2a03d Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 28 Dec 2022 11:55:04 +0100 Subject: [PATCH 16/25] change(ui): remove config(report) reducer, add mobx store --- frontend/app/components/Client/Client.js | 3 - .../Client/Notifications/Notifications.js | 50 --------- .../Client/Notifications/Notifications.tsx | 39 +++++++ frontend/app/duck/config.js | 61 ----------- frontend/app/duck/index.ts | 6 -- frontend/app/duck/member.js | 8 -- frontend/app/duck/target.js | 33 ------ frontend/app/duck/watchdogs.js | 101 ------------------ frontend/app/mstore/index.tsx | 5 + .../app/mstore/weeklyReportConfigStore.ts | 32 ++++++ frontend/app/services/BaseService.ts | 1 + frontend/app/services/ConfigService.ts | 17 +++ frontend/app/services/index.ts | 2 + 13 files changed, 96 insertions(+), 262 deletions(-) delete mode 100644 frontend/app/components/Client/Notifications/Notifications.js create mode 100644 frontend/app/components/Client/Notifications/Notifications.tsx delete mode 100644 frontend/app/duck/config.js delete mode 100644 frontend/app/duck/target.js delete mode 100644 frontend/app/duck/watchdogs.js create mode 100644 frontend/app/mstore/weeklyReportConfigStore.ts create mode 100644 frontend/app/services/ConfigService.ts diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index b2a6d8f7f..ab16c6b40 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -1,9 +1,7 @@ import React from 'react'; -import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { Switch, Route, Redirect } from 'react-router'; import { CLIENT_TABS, client as clientRoute } from 'App/routes'; -import { fetchList as fetchMemberList } from 'Duck/member'; import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; @@ -18,7 +16,6 @@ import PreferencesMenu from './PreferencesMenu'; import Notifications from './Notifications'; import Roles from './Roles'; -@connect(null, { fetchMemberList, }) @withRouter export default class Client extends React.PureComponent { constructor(props){ diff --git a/frontend/app/components/Client/Notifications/Notifications.js b/frontend/app/components/Client/Notifications/Notifications.js deleted file mode 100644 index 88855dd45..000000000 --- a/frontend/app/components/Client/Notifications/Notifications.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect } from 'react'; -import cn from 'classnames'; -import stl from './notifications.module.css'; -import { Checkbox, Toggler } from 'UI'; -import { connect } from 'react-redux'; -import { withRequest } from 'HOCs'; -import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'; -import withPageTitle from 'HOCs/withPageTitle'; - -function Notifications(props) { - const { config } = props; - - useEffect(() => { - props.fetchConfig(); - }, []); - - const onChange = () => { - const _config = { weeklyReport: !config.weeklyReport }; - props.editConfig(_config); - props.saveConfig(_config); - }; - - return ( -
-
{

{'Notifications'}

}
-
-
Weekly project summary
-
Receive wekly report for each project on email.
- - {/* */} - {/* */} -
-
- ); -} - -export default connect( - (state) => ({ - config: state.getIn(['config', 'options']), - }), - { fetchConfig, editConfig, saveConfig } -)(withPageTitle('Notifications - OpenReplay Preferences')(Notifications)); diff --git a/frontend/app/components/Client/Notifications/Notifications.tsx b/frontend/app/components/Client/Notifications/Notifications.tsx new file mode 100644 index 000000000..4a5d9a542 --- /dev/null +++ b/frontend/app/components/Client/Notifications/Notifications.tsx @@ -0,0 +1,39 @@ +import React, { useEffect } from 'react'; +import cn from 'classnames'; +import stl from './notifications.module.css'; +import { Toggler } from 'UI'; +import { useStore } from "App/mstore"; +import { observer } from 'mobx-react-lite' +import withPageTitle from 'HOCs/withPageTitle'; + +function Notifications() { + const { weeklyReportStore } = useStore() + + + useEffect(() => { + void weeklyReportStore.fetchReport() + }, []); + + const onChange = () => { + const newValue = !weeklyReportStore.weeklyReport + void weeklyReportStore.fetchEditReport(newValue) + }; + + return ( +
+
{

{'Notifications'}

}
+
+
Weekly project summary
+
Receive weekly report for each project on email.
+ +
+
+ ); +} + +export default withPageTitle('Notifications - OpenReplay Preferences')(observer(Notifications)) \ No newline at end of file diff --git a/frontend/app/duck/config.js b/frontend/app/duck/config.js deleted file mode 100644 index a445e3f50..000000000 --- a/frontend/app/duck/config.js +++ /dev/null @@ -1,61 +0,0 @@ -import { Map } from 'immutable'; -import { saveType, fetchType, editType } from './funcTools/crud/types'; -import { mergeReducers, success, array } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; - -const name = 'config' - -const FETCH = fetchType(name); -const SAVE = saveType(name); -const EDIT = editType(name); - -const FETCH_SUCCESS = success(FETCH); -const SAVE_SUCCESS = success(SAVE); - -const initialState = Map({ - options: { - weeklyReport: false - }, -}); - -const reducer = (state = initialState, action = {}) => { - switch(action.type) { - case FETCH_SUCCESS: - return state.set('options', action.data) - case SAVE_SUCCESS: - return state - case EDIT: - return state.set('options', action.config) - default: - return state; - } -} - -export const fetch = () => { - return { - types: array(FETCH), - call: client => client.get(`/config/weekly_report`), - } -} - -export const save = (config) => { - return { - types: array(SAVE), - call: client => client.post(`/config/weekly_report`, config), - } -} - -export const edit = (config) => { - return { - type: EDIT, - config - } -} - -export default mergeReducers( - reducer, - createRequestReducer({ - fetchRequest: FETCH, - saveRequest: SAVE, - }), -) \ No newline at end of file diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts index d33de09a0..1c62c36ff 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -5,7 +5,6 @@ import jwt from './jwt'; import user from './user'; import sessions from './sessions'; import assignments from './assignments'; -import target from './target'; import targetCustom from './targetCustom'; import filters from './filters'; import funnelFilters from './funnelFilters'; @@ -19,12 +18,10 @@ import site from './site'; import customFields from './customField'; import webhooks from './webhook'; import integrations from './integrations'; -import watchdogs from './watchdogs'; import rehydrate from './rehydrate'; import announcements from './announcements'; import errors from './errors'; import funnels from './funnels'; -import config from './config'; import roles from './roles'; import customMetrics from './customMetrics'; import search from './search'; @@ -35,7 +32,6 @@ const rootReducer = combineReducers({ user, sessions, assignments, - target, targetCustom, filters, funnelFilters, @@ -48,12 +44,10 @@ const rootReducer = combineReducers({ site, customFields, webhooks, - watchdogs, rehydrate, announcements, errors, funnels, - config, roles, customMetrics, search, diff --git a/frontend/app/duck/member.js b/frontend/app/duck/member.js index 31cccb395..290b53f3a 100644 --- a/frontend/app/duck/member.js +++ b/frontend/app/duck/member.js @@ -37,12 +37,4 @@ export function save(instance) { }; } -export function generateInviteLink(instance) { - return { - types: GENERATE_LINK.toArray(), - call: client => client.get(`/client/members/${ instance.id }/reset`), - id: instance.id - }; -} - export default reduceDucks(crudDuck, { initialState, reducer }).reducer; diff --git a/frontend/app/duck/target.js b/frontend/app/duck/target.js deleted file mode 100644 index b1f0e337b..000000000 --- a/frontend/app/duck/target.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Map } from 'immutable'; -import Target from 'Types/target'; -import { RequestTypes } from 'Duck/requestStateCreator'; -import crudDuckGenerator from 'Duck/tools/crudDuck'; -import { reduceDucks } from 'Duck/tools'; - -const FETCH_DEFINED = new RequestTypes('targets/FETCH_DEFINED'); - -const initialState = Map({ - definedPercent: 0, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_DEFINED.SUCCESS: - return state.set( - 'definedPercent', - Math.round((action.data.labeled / action.data.total) * 100), - ); - } - return state; -}; - -const crudDuck = crudDuckGenerator('target', Target); -export const { fetchList, init, edit, save, remove } = crudDuck.actions; -export default reduceDucks(crudDuck, { initialState, reducer }).reducer; - -export function fetchDefinedTargetsCount() { - return { - types: FETCH_DEFINED.toArray(), - call: client => client.get('/targets/count'), - }; -} diff --git a/frontend/app/duck/watchdogs.js b/frontend/app/duck/watchdogs.js deleted file mode 100644 index 87966264a..000000000 --- a/frontend/app/duck/watchdogs.js +++ /dev/null @@ -1,101 +0,0 @@ -import { List, Map } from 'immutable'; -import Watchdog from 'Types/watchdog'; -import { mergeReducers, success, array, request } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList, - createInit, - createEdit, - createRemove, - createSave, -} from './funcTools/crud'; - -const name = 'issue_type'; -const idKey = 'id'; -const SET_ACTIVE_TAB = 'watchdogs/SET_ACTIVE_TAB'; -const FETCH_WATCHDOG_STATUS = 'watchdogs/FETCH_WATCHDOG_STATUS'; -const FETCH_WATCHDOG_STATUS_SUCCESS = success(FETCH_WATCHDOG_STATUS); -const FETCH_RULES = 'watchdogs/FETCH_RULES'; -const FETCH_RULES_SUCCESS = success(FETCH_RULES); -const SAVE_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE'; -const EDIT_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE'; - -const initialState = Map({ - activeTab: Map(), - instance: Watchdog(), - list: List(), - rules: List(), - captureRate: Map() -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_ACTIVE_TAB: - return state.set('activeTab', action.instance); - case FETCH_RULES_SUCCESS: - return state.set('rules', action.data); - case FETCH_WATCHDOG_STATUS_SUCCESS: - case success(SAVE_CAPTURE_RATE): - return state.set('captureRate', Map(action.data)); - case request(SAVE_CAPTURE_RATE): - return state.mergeIn(['captureRate'], action.params); - case EDIT_CAPTURE_RATE: - return state.mergeIn(['captureRate'], {rate: action.rate}); - } - return state; -}; - - -export const fetchList = createFetchList(name); -export const init = createInit(name); -export const edit = createEdit(name); -export const save = createSave(name); -export const remove = createRemove(name); - -export function setActiveTab(instance) { - return { - type: SET_ACTIVE_TAB, - instance, - }; -} - -export const fetchRules = () => { - return { - types: array(FETCH_RULES), - call: client => client.get(`/watchdogs/rules`), - }; -} - -export default mergeReducers( - reducer, - createCRUDReducer(name, Watchdog, idKey), - createRequestReducer({ - fetchWatchdogStatus: FETCH_WATCHDOG_STATUS, - savingCaptureRate: SAVE_CAPTURE_RATE, - ...getCRUDRequestTypes(name), - }), -); - -export const saveCaptureRate = (params) => { - return { - params, - types: array(SAVE_CAPTURE_RATE), - call: client => client.post(`/sample_rate`, params), - } -} - -export const editCaptureRate = rate => { - return { - type: EDIT_CAPTURE_RATE, - rate - } -} - -export const fetchWatchdogStatus = () => { - return { - types: array(FETCH_WATCHDOG_STATUS), - call: client => client.get('/sample_rate'), - }; -} diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index a4fdbe884..707fb175a 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -15,6 +15,7 @@ import { errorService, notesService, recordingsService, + configService, } from 'App/services'; import SettingsStore from './settingsStore'; import AuditStore from './auditStore'; @@ -25,6 +26,7 @@ import NotesStore from './notesStore'; import BugReportStore from './bugReportStore' import RecordingsStore from './recordingsStore' import AssistMultiviewStore from './assistMultiviewStore'; +import WeeklyReportStore from './weeklyReportConfigStore' export class RootStore { dashboardStore: DashboardStore; @@ -41,6 +43,7 @@ export class RootStore { bugReportStore: BugReportStore; recordingsStore: RecordingsStore; assistMultiviewStore: AssistMultiviewStore; + weeklyReportStore: WeeklyReportStore constructor() { this.dashboardStore = new DashboardStore(); @@ -57,6 +60,7 @@ export class RootStore { this.bugReportStore = new BugReportStore(); this.recordingsStore = new RecordingsStore(); this.assistMultiviewStore = new AssistMultiviewStore(); + this.weeklyReportStore = new WeeklyReportStore(); } initClient() { @@ -70,6 +74,7 @@ export class RootStore { errorService.initClient(client); notesService.initClient(client) recordingsService.initClient(client); + configService.initClient(client); } } diff --git a/frontend/app/mstore/weeklyReportConfigStore.ts b/frontend/app/mstore/weeklyReportConfigStore.ts new file mode 100644 index 000000000..7b0eec8a6 --- /dev/null +++ b/frontend/app/mstore/weeklyReportConfigStore.ts @@ -0,0 +1,32 @@ +import { makeAutoObservable }from "mobx" +import { configService } from "App/services"; + +export default class weeklyReportConfigStore { + public weeklyReport = false + + constructor() { + makeAutoObservable(this) + } + + setReport(value: boolean) { + this.weeklyReport = value + } + + async fetchReport() { + try { + const { weeklyReport } = await configService.fetchWeeklyReport() + return this.setReport(weeklyReport) + } catch (e) { + console.error(e) + } + } + + async fetchEditReport(value: boolean) { + try { + const { weeklyReport } = await configService.editWeeklyReport({ weeklyReport: value }) + return this.setReport(weeklyReport) + } catch (e) { + console.error(e) + } + } +} \ No newline at end of file diff --git a/frontend/app/services/BaseService.ts b/frontend/app/services/BaseService.ts index 2af1897f8..5cca28d4e 100644 --- a/frontend/app/services/BaseService.ts +++ b/frontend/app/services/BaseService.ts @@ -1,4 +1,5 @@ import APIClient from 'App/api_client'; + export default class BaseService { client: APIClient; diff --git a/frontend/app/services/ConfigService.ts b/frontend/app/services/ConfigService.ts new file mode 100644 index 000000000..6afda4858 --- /dev/null +++ b/frontend/app/services/ConfigService.ts @@ -0,0 +1,17 @@ +import BaseService from './BaseService'; + +export interface WeeklyReport { + weeklyReport: boolean +} + +export default class ConfigService extends BaseService { + async fetchWeeklyReport(): Promise { + return this.client.get('/config/weekly_report') + .then(r => r.json()).then(j => j.data) + } + + async editWeeklyReport(config: WeeklyReport): Promise { + return this.client.post('/config/weekly_report', config) + .then(r => r.json()).then(j => j.data) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 2bcf5981e..5033c4ed3 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -7,6 +7,7 @@ import AuditService from './AuditService'; import ErrorService from "./ErrorService"; import NotesService from "./NotesService"; import RecordingsService from "./RecordingsService"; +import ConfigService from './ConfigService' export const dashboardService = new DashboardService(); export const metricService = new MetricService(); @@ -17,3 +18,4 @@ export const auditService = new AuditService(); export const errorService = new ErrorService(); export const notesService = new NotesService(); export const recordingsService = new RecordingsService(); +export const configService = new ConfigService(); \ No newline at end of file From 9feb0bb91fff6e967e02c0123b9a23b91fc67988 Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 28 Dec 2022 11:59:10 +0100 Subject: [PATCH 17/25] change(ui): small ref for code style --- .../Dashboard/components/FilterSeries/FilterSeries.tsx | 2 +- frontend/app/components/Session_/EventsBlock/EventsBlock.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index bcfd27406..2cf04078f 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -37,7 +37,7 @@ function FilterSeries(props: Props) { observeChanges() } - const onChangeEventsOrder = (e: any, { name, value }: any) => { + const onChangeEventsOrder = (_: any, { name, value }: any) => { series.filter.updateKey(name, value) observeChanges() } diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 0f0405cd5..daa68d85d 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -83,11 +83,11 @@ function EventsBlock(props: IProps) { } }, [currentTimeEventIndex]) - const onEventClick = (_: React.MouseEvent, event: any) => player.jump(event.time) + const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time) const onMouseOver = () => setMouseOver(true) const onMouseLeave = () => setMouseOver(false) - const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: any; parent: any }) => { + const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => { const isLastEvent = index === usedEvents.size - 1; const isLastInGroup = isLastEvent || usedEvents.get(index + 1)?.type === TYPES.LOCATION; const event = usedEvents.get(index); From 3dc3d736ac223e90f23c717b2aa35233215d765c Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 28 Dec 2022 14:10:59 +0100 Subject: [PATCH 18/25] change(ui): remove old announcements components and reducer --- frontend/app/Router.js | 2 - .../components/Announcements/Announcements.js | 99 ------------------- .../Announcements/ListItem/ListItem.js | 31 ------ .../Announcements/ListItem/index.js | 1 - .../ListItem/listItem.module.css | 5 - .../Announcements/announcements.module.css | 39 -------- .../app/components/Announcements/index.js | 1 - .../Session_/EventsBlock/EventsBlock.tsx | 2 +- .../AnnouncementModal/AnnouncementModal.tsx | 55 ----------- .../AnnouncementModal/ListItem/ListItem.tsx | 31 ------ .../AnnouncementModal/ListItem/index.ts | 1 - .../ListItem/listItem.module.css | 5 - .../shared/AnnouncementModal/index.ts | 1 - .../DevTools/ConsolePanel/ConsolePanel.tsx | 2 +- .../StackEventPanel/StackEventPanel.tsx | 2 +- frontend/app/duck/announcements.js | 45 --------- frontend/app/duck/index.ts | 2 - frontend/app/duck/member.js | 2 +- .../useCellMeasurerCache.ts | 0 19 files changed, 4 insertions(+), 322 deletions(-) delete mode 100644 frontend/app/components/Announcements/Announcements.js delete mode 100644 frontend/app/components/Announcements/ListItem/ListItem.js delete mode 100644 frontend/app/components/Announcements/ListItem/index.js delete mode 100644 frontend/app/components/Announcements/ListItem/listItem.module.css delete mode 100644 frontend/app/components/Announcements/announcements.module.css delete mode 100644 frontend/app/components/Announcements/index.js delete mode 100644 frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx delete mode 100644 frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx delete mode 100644 frontend/app/components/shared/AnnouncementModal/ListItem/index.ts delete mode 100644 frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css delete mode 100644 frontend/app/components/shared/AnnouncementModal/index.ts delete mode 100644 frontend/app/duck/announcements.js rename frontend/app/{components/shared/DevTools => hooks}/useCellMeasurerCache.ts (100%) diff --git a/frontend/app/Router.js b/frontend/app/Router.js index e99ee2546..b8832dbd4 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -8,7 +8,6 @@ import { fetchUserInfo } from 'Duck/user'; import withSiteIdUpdater from 'HOCs/withSiteIdUpdater'; import Header from 'Components/Header/Header'; import { fetchList as fetchSiteList } from 'Duck/site'; -import { fetchList as fetchAnnouncements } from 'Duck/announcements'; import { fetchList as fetchAlerts } from 'Duck/alerts'; import { withStore } from 'App/mstore'; @@ -115,7 +114,6 @@ const MULTIVIEW_INDEX_PATH = routes.multiviewIndex(); fetchTenants, setSessionPath, fetchSiteList, - fetchAnnouncements, fetchAlerts, } ) diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js deleted file mode 100644 index 55306e643..000000000 --- a/frontend/app/components/Announcements/Announcements.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import stl from './announcements.module.css'; -import ListItem from './ListItem'; -import { connect } from 'react-redux'; -import { SlideModal, Icon, NoContent, Tooltip } from 'UI'; -import { fetchList, setLastRead } from 'Duck/announcements'; -import withToggle from 'Components/hocs/withToggle'; -import { withRouter } from 'react-router-dom'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; - -@withToggle('visible', 'toggleVisisble') -@withRouter -class Announcements extends React.Component { - - navigateToUrl = url => { - if (url) { - if (url.startsWith(window.env.ORIGIN || window.location.origin)) { - const { history } = this.props; - var path = new URL(url).pathname - if (path.includes('/metrics')) { - const { siteId, sites } = this.props; - const activeSite = sites.find(s => s.id == siteId); - history.push(`/${activeSite.id + path}`); - } else { - history.push(path) - } - } else { - window.open(url, "_blank") - } - this.toggleModal() - } - } - - toggleModal = () => { - if (!this.props.visible) { - const { setLastRead, fetchList } = this.props; - fetchList().then(() => { setTimeout(() => { setLastRead() }, 5000); }); - } - this.props.toggleVisisble(!this.props.visible); - } - - render() { - const { announcements, visible, loading } = this.props; - const unReadNotificationsCount = announcements.filter(({viewed}) => !viewed).size - - return ( -
- -
-
- { unReadNotificationsCount } -
- -
-
- - - - -
No announcements to show.
-
- } - size="small" - show={ !loading && announcements.size === 0 } - > - { - announcements.map(item => ( - - )) - } - -
- } - /> -
- ); - } -} - -export default connect(state => ({ - announcements: state.getIn(['announcements', 'list']), - loading: state.getIn(['announcements', 'fetchList', 'loading']), - siteId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), -}), { fetchList, setLastRead })(Announcements); \ No newline at end of file diff --git a/frontend/app/components/Announcements/ListItem/ListItem.js b/frontend/app/components/Announcements/ListItem/ListItem.js deleted file mode 100644 index dd777c719..000000000 --- a/frontend/app/components/Announcements/ListItem/ListItem.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Button, Label } from 'UI'; -import stl from './listItem.module.css'; - -const ListItem = ({ announcement, onButtonClick }) => { - return ( -
-
-
{announcement.createdAt && announcement.createdAt.toFormat('LLL dd, yyyy')}
- -
- {announcement.imageUrl && - - } -
-

{announcement.title}

-
{announcement.description}
- {announcement.buttonUrl && - - } -
-
- ) -} - -export default ListItem diff --git a/frontend/app/components/Announcements/ListItem/index.js b/frontend/app/components/Announcements/ListItem/index.js deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/Announcements/ListItem/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/Announcements/ListItem/listItem.module.css b/frontend/app/components/Announcements/ListItem/listItem.module.css deleted file mode 100644 index 5bc3a44c8..000000000 --- a/frontend/app/components/Announcements/ListItem/listItem.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - background-color: white; - margin-bottom: 20px; - padding: 15px; -} \ No newline at end of file diff --git a/frontend/app/components/Announcements/announcements.module.css b/frontend/app/components/Announcements/announcements.module.css deleted file mode 100644 index 5a3704af2..000000000 --- a/frontend/app/components/Announcements/announcements.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.wrapper { - position: relative; -} - -.button { - position: relative; - cursor: pointer; - display: flex; - align-items: center; - padding: 0 15px; - height: 50px; - transition: all 0.3s; - - &:hover { - background-color: $gray-lightest; - transition: all 0.2s; - } - - &[data-active=true] { - background-color: $gray-lightest; - } -} - -.counter { - position: absolute; - top: 8px; - left: 24px; - background-color: #CC0000; - color: white; - font-size: 9px; - font-weight: 300; - min-width: 16px; - height: 16px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - padding: 3px; -} diff --git a/frontend/app/components/Announcements/index.js b/frontend/app/components/Announcements/index.js deleted file mode 100644 index faeffcfcd..000000000 --- a/frontend/app/components/Announcements/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Announcements'; \ No newline at end of file diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index daa68d85d..8df9be6ff 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -13,7 +13,7 @@ import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { RootStore } from 'App/duck' import { List as ImmList } from 'immutable' -import useCellMeasurerCache from 'Components/shared/DevTools/useCellMeasurerCache' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' interface IProps { setEventFilter: (filter: { query: string }) => void diff --git a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx b/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx deleted file mode 100644 index 5f69de976..000000000 --- a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Button, NoContent } from 'UI'; -import { connect } from 'react-redux'; -import { fetchList, setLastRead } from 'Duck/announcements'; -import cn from 'classnames'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import ListItem from './ListItem' - -interface Props { - unReadNotificationsCount: number; - setLastRead: Function; - list: any; -} -function AnnouncementModal(props: Props) { - const { list, unReadNotificationsCount } = props; - - // const onClear = (notification: any) => { - // console.log('onClear', notification); - // props.setViewed(notification.notificationId) - // } - - return ( -
-
-
Announcements
-
- -
- - -
- } - subtext="There are no alerts to show." - // show={ !loading && unReadNotificationsCount === 0 } - size="small" - > - {list.map((item: any, i: any) => ( -
- {/* onClear(item)} loading={false} /> */} -
- ))} - -
- - ); -} - -export default connect((state: any) => ({ - list: state.getIn(['announcements', 'list']), -}), { - fetchList, - setLastRead, -})(AnnouncementModal); \ No newline at end of file diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx b/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx deleted file mode 100644 index dd777c719..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Button, Label } from 'UI'; -import stl from './listItem.module.css'; - -const ListItem = ({ announcement, onButtonClick }) => { - return ( -
-
-
{announcement.createdAt && announcement.createdAt.toFormat('LLL dd, yyyy')}
- -
- {announcement.imageUrl && - - } -
-

{announcement.title}

-
{announcement.description}
- {announcement.buttonUrl && - - } -
-
- ) -} - -export default ListItem diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts b/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css b/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css deleted file mode 100644 index 5bc3a44c8..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - background-color: white; - margin-bottom: 20px; - padding: 15px; -} \ No newline at end of file diff --git a/frontend/app/components/shared/AnnouncementModal/index.ts b/frontend/app/components/shared/AnnouncementModal/index.ts deleted file mode 100644 index b9af0fc52..000000000 --- a/frontend/app/components/shared/AnnouncementModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AnnouncementModal'; \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index 4719914ba..1c7e9c425 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -12,7 +12,7 @@ import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorD import { useModal } from 'App/components/Modal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter' -import useCellMeasurerCache from '../useCellMeasurerCache' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' const ALL = 'ALL'; const INFO = 'INFO'; diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index 366750b51..b97e82e97 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -12,7 +12,7 @@ import StackEventRow from 'Shared/DevTools/StackEventRow'; import StackEventModal from '../StackEventModal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter' -import useCellMeasurerCache from '../useCellMeasurerCache' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' const INDEX_KEY = 'stackEvent'; const ALL = 'ALL'; diff --git a/frontend/app/duck/announcements.js b/frontend/app/duck/announcements.js deleted file mode 100644 index 3a7612ee7..000000000 --- a/frontend/app/duck/announcements.js +++ /dev/null @@ -1,45 +0,0 @@ -import { List, Map } from 'immutable'; -import Announcement from 'Types/announcement'; -import { RequestTypes } from './requestStateCreator'; - -import { mergeReducers } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList -} from './funcTools/crud'; - -const name = 'announcement'; -const idKey = 'id'; - -const SET_LAST_READ = new RequestTypes('announcement/SET_LAST_READ'); - -const initialState = Map({ - list: List() -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_LAST_READ.SUCCESS: - return state.update('list', (list) => list.map(i => ({...i.toJS(), viewed: true }))); - } - return state; -}; - -export function setLastRead() { - return { - types: SET_LAST_READ.toArray(), - call: client => client.get(`/announcements/view`), - }; -} - -export const fetchList = createFetchList(name); - -export default mergeReducers( - reducer, - createCRUDReducer(name, Announcement, idKey), - createRequestReducer({ - ...getCRUDRequestTypes(name), - }), -); \ No newline at end of file diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts index 1c62c36ff..96e150920 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -19,7 +19,6 @@ import customFields from './customField'; import webhooks from './webhook'; import integrations from './integrations'; import rehydrate from './rehydrate'; -import announcements from './announcements'; import errors from './errors'; import funnels from './funnels'; import roles from './roles'; @@ -45,7 +44,6 @@ const rootReducer = combineReducers({ customFields, webhooks, rehydrate, - announcements, errors, funnels, roles, diff --git a/frontend/app/duck/member.js b/frontend/app/duck/member.js index 290b53f3a..ce12c1659 100644 --- a/frontend/app/duck/member.js +++ b/frontend/app/duck/member.js @@ -1,7 +1,7 @@ import { Map } from 'immutable'; import Member from 'Types/member'; import crudDuckGenerator from './tools/crudDuck'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; +import { RequestTypes } from 'Duck/requestStateCreator'; import { reduceDucks } from 'Duck/tools'; const GENERATE_LINK = new RequestTypes('member/GENERATE_LINK'); diff --git a/frontend/app/components/shared/DevTools/useCellMeasurerCache.ts b/frontend/app/hooks/useCellMeasurerCache.ts similarity index 100% rename from frontend/app/components/shared/DevTools/useCellMeasurerCache.ts rename to frontend/app/hooks/useCellMeasurerCache.ts From 22cdb4f4e7b367e8e73cd2a24fe9caf8b388ce36 Mon Sep 17 00:00:00 2001 From: sylenien Date: Wed, 28 Dec 2022 14:41:04 +0100 Subject: [PATCH 19/25] change(ui): remove bugfinder components and store --- frontend/.storybook/config.DEPRECATED.js | 2 - frontend/app/Router.js | 6 +- frontend/app/api_client.js | 1 - .../BugFinder/AutoComplete/AutoComplete.js | 199 ------------------ .../BugFinder/AutoComplete/DropdownItem.js | 10 - .../AutoComplete/autoComplete.module.css | 64 ------ .../AutoComplete/dropdownItem.module.css | 11 - .../BugFinder/AutoComplete/index.js | 1 - .../app/components/BugFinder/BugFinder.js | 131 ------------ .../app/components/BugFinder/DateRange.js | 31 --- .../BugFinder/FilterSelectionButton.js | 14 -- .../BugFinder/Filters/SortDropdown.js | 37 ---- .../app/components/BugFinder/Filters/index.js | 1 - .../BugFinder/Filters/sortDropdown.module.css | 23 -- frontend/app/components/BugFinder/Insights.js | 21 -- .../app/components/BugFinder/ListHeader.js | 10 - .../BugFinder/SessionList/SessionList.js | 150 ------------- .../SessionList/SessionListFooter.js | 29 --- .../SessionList/SessionListHeader.js | 78 ------- .../components/BugFinder/SessionList/index.js | 1 - .../SessionList/sessionList.module.css | 12 -- .../SessionList/sessionListFooter.module.css | 18 -- .../BugFinder/SessionsMenu/SessionsMenu.js | 72 ------- .../BugFinder/SessionsMenu/index.js | 1 - .../SessionsMenu/sessionMenu.module.css | 29 --- .../components/BugFinder/TabItem/TabItem.js | 27 --- .../app/components/BugFinder/TabItem/index.js | 1 - .../BugFinder/TabItem/tabItem.module.css | 23 -- .../components/BugFinder/bugFinder.module.css | 44 ---- .../components/BugFinder/bugFinder.stories.js | 70 ------ .../filterSelectionButton.module.css | 24 --- frontend/app/components/BugFinder/index.js | 1 - .../components/BugFinder/insights.module.css | 18 -- .../BugFinder/listHeader.module.css | 7 - frontend/app/components/Errors/List/List.js | 3 +- .../shared/SessionStack/SessionStack.js | 42 ---- .../components/shared/SessionStack/index.js | 1 - .../SessionStack/sessionStack.module.css | 18 -- frontend/app/types/filter/savedFilter.js | 1 - frontend/app/types/session/session.ts | 2 - frontend/app/types/ts/search.ts | 1 - 41 files changed, 4 insertions(+), 1231 deletions(-) delete mode 100644 frontend/app/components/BugFinder/AutoComplete/AutoComplete.js delete mode 100644 frontend/app/components/BugFinder/AutoComplete/DropdownItem.js delete mode 100644 frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css delete mode 100644 frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css delete mode 100644 frontend/app/components/BugFinder/AutoComplete/index.js delete mode 100644 frontend/app/components/BugFinder/BugFinder.js delete mode 100644 frontend/app/components/BugFinder/DateRange.js delete mode 100644 frontend/app/components/BugFinder/FilterSelectionButton.js delete mode 100644 frontend/app/components/BugFinder/Filters/SortDropdown.js delete mode 100644 frontend/app/components/BugFinder/Filters/index.js delete mode 100644 frontend/app/components/BugFinder/Filters/sortDropdown.module.css delete mode 100644 frontend/app/components/BugFinder/Insights.js delete mode 100644 frontend/app/components/BugFinder/ListHeader.js delete mode 100644 frontend/app/components/BugFinder/SessionList/SessionList.js delete mode 100644 frontend/app/components/BugFinder/SessionList/SessionListFooter.js delete mode 100644 frontend/app/components/BugFinder/SessionList/SessionListHeader.js delete mode 100644 frontend/app/components/BugFinder/SessionList/index.js delete mode 100644 frontend/app/components/BugFinder/SessionList/sessionList.module.css delete mode 100644 frontend/app/components/BugFinder/SessionList/sessionListFooter.module.css delete mode 100644 frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js delete mode 100644 frontend/app/components/BugFinder/SessionsMenu/index.js delete mode 100644 frontend/app/components/BugFinder/SessionsMenu/sessionMenu.module.css delete mode 100644 frontend/app/components/BugFinder/TabItem/TabItem.js delete mode 100644 frontend/app/components/BugFinder/TabItem/index.js delete mode 100644 frontend/app/components/BugFinder/TabItem/tabItem.module.css delete mode 100644 frontend/app/components/BugFinder/bugFinder.module.css delete mode 100644 frontend/app/components/BugFinder/bugFinder.stories.js delete mode 100644 frontend/app/components/BugFinder/filterSelectionButton.module.css delete mode 100644 frontend/app/components/BugFinder/index.js delete mode 100644 frontend/app/components/BugFinder/insights.module.css delete mode 100644 frontend/app/components/BugFinder/listHeader.module.css delete mode 100644 frontend/app/components/shared/SessionStack/SessionStack.js delete mode 100644 frontend/app/components/shared/SessionStack/index.js delete mode 100644 frontend/app/components/shared/SessionStack/sessionStack.module.css diff --git a/frontend/.storybook/config.DEPRECATED.js b/frontend/.storybook/config.DEPRECATED.js index fad172b6f..ba3f6eb05 100644 --- a/frontend/.storybook/config.DEPRECATED.js +++ b/frontend/.storybook/config.DEPRECATED.js @@ -11,14 +11,12 @@ const withProvider = (story) => ( // const req = require.context('../app/components/ui', true, /\.stories\.js$/); // const issues = require.context('../app/components/Session/Issues', true, /\.stories\.js$/); -// const bugFinder = require.context('../app/components/BugFinder', true, /\.stories\.js$/); addDecorator(withProvider); addDecorator(story => {story()}); // function loadStories() { // req.keys().forEach(filename => req(filename)); -// bugFinder.keys().forEach(filename => bugFinder(filename)); // } // configure(loadStories, module); diff --git a/frontend/app/Router.js b/frontend/app/Router.js index b8832dbd4..955cfa0b3 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -29,7 +29,7 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession')); const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding')); const ClientPure = lazy(() => import('Components/Client/Client')); const AssistPure = lazy(() => import('Components/Assist')); -const BugFinderPure = lazy(() => import('Components/Overview')); +const SessionsOverviewPure = lazy(() => import('Components/Overview')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); @@ -37,7 +37,7 @@ const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDeta const FunnelPagePure = lazy(() => import('Components/Funnels/FunnelPage')); const MultiviewPure = lazy(() => import('Components/Session_/Multiview/Multiview.tsx')); -const BugFinder = withSiteIdUpdater(BugFinderPure); +const SessionsOverview = withSiteIdUpdater(SessionsOverviewPure); const Dashboard = withSiteIdUpdater(DashboardPure); const Session = withSiteIdUpdater(SessionPure); const LiveSession = withSiteIdUpdater(LiveSessionPure); @@ -235,7 +235,7 @@ class Router extends React.Component { - + } /> diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index b60c0dbb7..421074036 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -88,7 +88,6 @@ export default class APIClient { if ( path !== '/targets_temp' && !path.includes('/metadata/session_search') && - !path.includes('/watchdogs/rules') && !path.includes('/assist/credentials') && !!this.siteId && siteIdRequiredPaths.some(sidPath => path.startsWith(sidPath)) diff --git a/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js b/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js deleted file mode 100644 index 59ed9d9e9..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js +++ /dev/null @@ -1,199 +0,0 @@ -import React from 'react'; -import APIClient from 'App/api_client'; -import cn from 'classnames'; -import { Input, Icon } from 'UI'; -import { debounce } from 'App/utils'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; -import EventSearchInput from 'Shared/EventSearchInput'; -import stl from './autoComplete.module.css'; -import FilterItem from '../CustomFilters/FilterItem'; - -const TYPE_TO_SEARCH_MSG = "Start typing to search..."; -const NO_RESULTS_MSG = "No results found."; -const SOME_ERROR_MSG = "Some error occured."; -const defaultValueToText = value => value; -const defaultOptionMapping = (values, valueToText) => values.map(value => ({ text: valueToText(value), value })); - -const hiddenStyle = { - whiteSpace: 'pre-wrap', - opacity: 0, position: 'fixed', left: '-3000px' -}; - -let pasted = false; -let changed = false; - -class AutoComplete extends React.PureComponent { - static defaultProps = { - method: 'GET', - params: {}, - } - - state = { - values: [], - noResultsMessage: TYPE_TO_SEARCH_MSG, - ddOpen: false, - query: this.props.value, - loading: false, - error: false - } - - componentWillReceiveProps(newProps) { - if (this.props.value !== newProps.value) { - this.setState({ query: newProps.value}); - } - } - - onClickOutside = () => { - this.setState({ ddOpen: false }); - } - - requestValues = (q) => { - const { params, endpoint, method } = this.props; - this.setState({ - loading: true, - error: false, - }); - return new APIClient()[ method.toLowerCase() ](endpoint, { ...params, q }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - this.setError(); - } else { - this.setState({ - ddOpen: true, - values: data, - loading: false, - noResultsMessage: NO_RESULTS_MSG, - }); - } - }) - .catch(this.setError); - } - - debouncedRequestValues = debounce(this.requestValues, 1000) - - setError = () => this.setState({ - loading: false, - error: true, - noResultsMessage: SOME_ERROR_MSG, - }) - - - onInputChange = ({ target: { value } }) => { - changed = true; - this.setState({ query: value, updated: true }) - const _value = value ? value.trim() : undefined; - if (_value !== '' && _value !== ' ') { - this.debouncedRequestValues(_value) - } - } - - onBlur = ({ target: { value } }) => { - // to avoid sending unnecessary request on focus in/out without changing - if (!changed && !pasted) return; - - value = pasted ? this.hiddenInput.value : value; - const { onSelect, name } = this.props; - if (value !== this.props.value) { - const _value = value ? value.trim() : undefined; - onSelect(null, {name, value: _value}); - } - - changed = false; - pasted = false; - } - - onItemClick = (e, item) => { - e.stopPropagation(); - e.preventDefault(); - const { onSelect, name } = this.props; - - this.setState({ query: item.value, ddOpen: false}) - onSelect(e, {name, ...item.toJS()}); - } - - render() { - const { ddOpen, query, loading, values } = this.state; - const { - optionMapping = defaultOptionMapping, - valueToText = defaultValueToText, - placeholder = 'Type to search...', - headerText = '', - fullWidth = false, - onRemoveValue = () => {}, - onAddValue = () => {}, - showCloseButton = false, - } = this.props; - - const options = optionMapping(values, valueToText) - - return ( - - {/* */} -
- this.setState({ddOpen: true})} - onChange={ this.onInputChange } - onBlur={ this.onBlur } - value={ query } - autoFocus={ true } - type="text" - placeholder={ placeholder } - onPaste={(e) => { - const text = e.clipboardData.getData('Text'); - this.hiddenInput.value = text; - pasted = true; // to use only the hidden input - } } - autocomplete="do-not-autofill-bad-chrome" - /> -
- { showCloseButton ? : or} -
-
- - {showCloseButton &&
or
} - {/* this.setState({ddOpen: true})} - value={ query } - // icon="search" - label={{ basic: true, content:
test
}} - labelPosition='right' - loading={ loading } - autoFocus={ true } - type="search" - placeholder={ placeholder } - onPaste={(e) => { - const text = e.clipboardData.getData('Text'); - this.hiddenInput.value = text; - pasted = true; // to use only the hidden input - } } - /> */} - - { ddOpen && options.length > 0 && -
- { headerText && headerText } - { - options.map(item => ( - this.onItemClick(e, item) } - /> - )) - } -
- } -
- ); - } -} - -export default AutoComplete; diff --git a/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js b/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js deleted file mode 100644 index dc2b97304..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import stl from './dropdownItem.module.css'; - -const DropdownItem = ({ value, onSelect }) => { - return ( -
{ value }
- ); -}; - -export default DropdownItem; diff --git a/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css b/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css deleted file mode 100644 index 09a9a6571..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css +++ /dev/null @@ -1,64 +0,0 @@ -.menu { - border-radius: 0 0 3px 3px; - box-shadow: 0 2px 10px 0 $gray-light; - padding: 20px; - background-color: white; - max-height: 350px; - overflow-y: auto; - position: absolute; - top: 28px; - left: 0; - width: 500px; - z-index: 99; -} - -.searchInput { - & input { - font-size: 13px !important; - padding: 5px !important; - color: $gray-darkest !important; - font-size: 14px !important; - background-color: rgba(255, 255, 255, 0.8) !important; - - & .label { - padding: 0px !important; - display: flex; - align-items: center; - justify-content: center; - } - } - height: 28px !important; - width: 280px; - color: $gray-darkest !important; -} - -.fullWidth { - width: 100% !important; -} - -.inputWrapper { - border: solid thin $gray-light !important; - border-radius: 3px; - border-radius: 3px; - display: flex; - align-items: center; - & input { - height: 28px; - font-size: 13px !important; - padding: 0 5px !important; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - - & .right { - height: 28px; - display: flex; - align-items: center; - padding: 0 5px; - background-color: $gray-lightest; - border-left: solid thin $gray-light !important; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - cursor: pointer; - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css b/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css deleted file mode 100644 index f5646a470..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.wrapper { - padding: 8px; - border-bottom: solid thin rgba(0, 0, 0, 0.05); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; - &:hover { - background-color: $active-blue; - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/AutoComplete/index.js b/frontend/app/components/BugFinder/AutoComplete/index.js deleted file mode 100644 index fa63241a4..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AutoComplete'; \ No newline at end of file diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js deleted file mode 100644 index 2c31a3ca8..000000000 --- a/frontend/app/components/BugFinder/BugFinder.js +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import withPageTitle from 'HOCs/withPageTitle'; -import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions'; -import { applyFilter, clearEvents, addAttribute } from 'Duck/filters'; -import { KEYS } from 'Types/filter/customFilter'; -import SessionList from './SessionList'; -import stl from './bugFinder.module.css'; -import withLocationHandlers from 'HOCs/withLocationHandlers'; -import { fetch as fetchFilterVariables } from 'Duck/sources'; -import { fetchSources } from 'Duck/customField'; -import { setActiveTab } from 'Duck/search'; -import SessionsMenu from './SessionsMenu/SessionsMenu'; -import NoSessionsMessage from 'Shared/NoSessionsMessage'; -import SessionSearch from 'Shared/SessionSearch'; -import MainSearchBar from 'Shared/MainSearchBar'; -import { clearSearch, fetchSessions, addFilterByKeyAndValue } from 'Duck/search'; -import { FilterKey } from 'Types/filter/filterType'; - -const weakEqual = (val1, val2) => { - if (!!val1 === false && !!val2 === false) return true; - if (!val1 !== !val2) return false; - return `${val1}` === `${val2}`; -}; - -const allowedQueryKeys = [ - 'userOs', - 'userId', - 'userBrowser', - 'userDevice', - 'userCountry', - 'startDate', - 'endDate', - 'minDuration', - 'maxDuration', - 'referrer', - 'sort', - 'order', -]; - -@withLocationHandlers() -@connect( - (state) => ({ - filter: state.getIn(['filters', 'appliedFilter']), - variables: state.getIn(['customFields', 'list']), - sources: state.getIn(['customFields', 'sources']), - filterValues: state.get('filterValues'), - favoriteList: state.getIn(['sessions', 'favoriteList']), - currentProjectId: state.getIn(['site', 'siteId']), - sites: state.getIn(['site', 'list']), - watchdogs: state.getIn(['watchdogs', 'list']), - activeFlow: state.getIn(['filters', 'activeFlow']), - sessions: state.getIn(['sessions', 'list']), - }), - { - fetchFavoriteSessionList, - applyFilter, - addAttribute, - fetchFilterVariables, - fetchSources, - clearEvents, - setActiveTab, - clearSearch, - fetchSessions, - addFilterByKeyAndValue, - } -) -@withPageTitle('Sessions - OpenReplay') -export default class BugFinder extends React.PureComponent { - state = { showRehydratePanel: false }; - constructor(props) { - super(props); - - // TODO should cache the response - // props.fetchSources().then(() => { - // defaultFilters[6] = { - // category: 'Collaboration', - // type: 'CUSTOM', - // keys: this.props.sources.filter(({type}) => type === 'collaborationTool').map(({ label, key }) => ({ type: 'CUSTOM', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // defaultFilters[7] = { - // category: 'Logging Tools', - // type: 'ERROR', - // keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // }); - // if (props.sessions.size === 0) { - // props.fetchSessions(); - // } - - const queryFilter = this.props.query.all(allowedQueryKeys); - if (queryFilter.hasOwnProperty('userId')) { - props.addFilterByKeyAndValue(FilterKey.USERID, queryFilter.userId); - } else { - if (props.sessions.size === 0) { - props.fetchSessions(); - } - } - } - - toggleRehydratePanel = () => { - this.setState({ showRehydratePanel: !this.state.showRehydratePanel }); - }; - - setActiveTab = (tab) => { - this.props.setActiveTab(tab); - }; - - render() { - const { showRehydratePanel } = this.state; - - return ( -
-
-
- -
-
- -
- - -
- -
-
-
- ); - } -} diff --git a/frontend/app/components/BugFinder/DateRange.js b/frontend/app/components/BugFinder/DateRange.js deleted file mode 100644 index 9a6e77f12..000000000 --- a/frontend/app/components/BugFinder/DateRange.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { applyFilter } from 'Duck/search'; -import { fetchList as fetchFunnelsList } from 'Duck/funnels'; -import DateRangeDropdown from 'Shared/DateRangeDropdown'; - -@connect(state => ({ - filter: state.getIn([ 'search', 'instance' ]), -}), { - applyFilter, fetchFunnelsList -}) -export default class DateRange extends React.PureComponent { - onDateChange = (e) => { - // this.props.fetchFunnelsList(e.rangeValue) - this.props.applyFilter(e) - } - render() { - const { filter: { rangeValue, startDate, endDate }, className } = this.props; - - return ( - - ); - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/FilterSelectionButton.js b/frontend/app/components/BugFinder/FilterSelectionButton.js deleted file mode 100644 index 9854b29a3..000000000 --- a/frontend/app/components/BugFinder/FilterSelectionButton.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import stl from './filterSelectionButton.module.css'; - -const FilterSelectionButton = ({ label }) => { - return ( -
- { label } - -
- ); -}; - -export default FilterSelectionButton; diff --git a/frontend/app/components/BugFinder/Filters/SortDropdown.js b/frontend/app/components/BugFinder/Filters/SortDropdown.js deleted file mode 100644 index 398902ec5..000000000 --- a/frontend/app/components/BugFinder/Filters/SortDropdown.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import Select from 'Shared/Select'; -import { Icon } from 'UI'; -import { sort } from 'Duck/sessions'; -import { applyFilter } from 'Duck/search'; -import stl from './sortDropdown.module.css'; - -@connect(null, { sort, applyFilter }) -export default class SortDropdown extends React.PureComponent { - state = { value: null } - sort = ({ value }) => { - value = value.value - this.setState({ value: value }) - const [ sort, order ] = value.split('-'); - const sign = order === 'desc' ? -1 : 1; - this.props.applyFilter({ order, sort }); - - this.props.sort(sort, sign) - setTimeout(() => this.props.sort(sort, sign), 3000); //AAA - } - - render() { - const { options } = this.props; - return ( -