diff --git a/api/Dockerfile b/api/Dockerfile index cdd8cd295..a6077fd13 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base nodejs npm tini RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg diff --git a/api/app.py b/api/app.py index 959f1ef8f..4fd042d1a 100644 --- a/api/app.py +++ b/api/app.py @@ -13,7 +13,7 @@ from routers.crons import core_crons from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api -app = FastAPI(root_path="/api") +app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) @app.middleware('http') diff --git a/api/app_alerts.py b/api/app_alerts.py index 57bfcd55d..4e05ab1a8 100644 --- a/api/app_alerts.py +++ b/api/app_alerts.py @@ -6,7 +6,7 @@ from fastapi import FastAPI from chalicelib.core import alerts_processor -app = FastAPI() +app = FastAPI(root_path="/alerts", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) print("============= ALERTS =============") diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index efbc7b5c6..6fc8bcd90 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -35,9 +35,10 @@ def get_live_sessions_ws(project_id, body: schemas.LiveSessionsSearchPayloadSche } for f in body.filters: if f.type == schemas.LiveFilterType.metadata: - data["filter"][f.source] = f.value + data["filter"][f.source] = {"values": f.value, "operator": f.operator} + else: - data["filter"][f.type.value] = f.value + data["filter"][f.type.value] = {"values": f.value, "operator": f.operator} return __get_live_sessions_ws(project_id=project_id, data=data) diff --git a/api/chalicelib/core/sessions.py b/api/chalicelib/core/sessions.py index 8dc87c90d..738a5e3d9 100644 --- a/api/chalicelib/core/sessions.py +++ b/api/chalicelib/core/sessions.py @@ -390,14 +390,14 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema): return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.request_details, - schemas.EventType.graphql_details] \ + schemas.EventType.graphql] \ or event.type in [schemas.PerformanceEventType.location_dom_complete, schemas.PerformanceEventType.location_largest_contentful_paint_time, schemas.PerformanceEventType.location_ttfb, schemas.PerformanceEventType.location_avg_cpu_load, schemas.PerformanceEventType.location_avg_memory_usage ] and (event.source is None or len(event.source) == 0) \ - or event.type in [schemas.EventType.request_details, schemas.EventType.graphql_details] and ( + or event.type in [schemas.EventType.request_details, schemas.EventType.graphql] and ( event.filters is None or len(event.filters) == 0)) @@ -698,12 +698,12 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr event_where.append( _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k})s", event.value, value_key=e_k)) - elif event_type == events.event_type.GRAPHQL.ui_type: - event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " - if not is_any: - event_where.append( - _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, - value_key=e_k)) + # elif event_type == events.event_type.GRAPHQL.ui_type: + # event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " + # if not is_any: + # event_where.append( + # _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, + # value_key=e_k)) elif event_type == events.event_type.STATEACTION.ui_type: event_from = event_from % f"{events.event_type.STATEACTION.table} AS main " if not is_any: @@ -891,7 +891,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr print(f"undefined FETCH filter: {f.type}") if not apply: continue - elif event_type == schemas.EventType.graphql_details: + elif event_type == schemas.EventType.graphql: event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " for j, f in enumerate(event.filters): is_any = _isAny_opreator(f.operator) diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index 78499860b..1535534c8 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -241,7 +241,6 @@ def get(user_id, tenant_id): (CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member, - api_key, TRUE AS has_password FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id WHERE diff --git a/api/chalicelib/core/weekly_report.py b/api/chalicelib/core/weekly_report.py index 952bf584b..afb0843ce 100644 --- a/api/chalicelib/core/weekly_report.py +++ b/api/chalicelib/core/weekly_report.py @@ -32,7 +32,8 @@ def cron(): if not helper.has_smtp(): print("!!! No SMTP configuration found, ignoring weekly report") return - with pg_client.PostgresClient(long_query=True) as cur: + _now = TimeUTC.now() + with pg_client.PostgresClient(unlimited_query=True) as cur: params = {"tomorrow": TimeUTC.midnight(delta_days=1), "3_days_ago": TimeUTC.midnight(delta_days=-3), "1_week_ago": TimeUTC.midnight(delta_days=-7), @@ -86,6 +87,9 @@ def cron(): AND issues.timestamp >= %(5_week_ago)s ) AS month_1_issues ON (TRUE);"""), params) projects_data = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report query: {_now2 - _now} ms") + _now = _now2 emails_to_send = [] for p in projects_data: params["project_id"] = p["project_id"] @@ -116,6 +120,9 @@ def cron(): ) AS timestamp_i ORDER BY timestamp_i;""", params)) days_partition = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 max_days_partition = max(x['issues_count'] for x in days_partition) for d in days_partition: if max_days_partition <= 0: @@ -132,6 +139,9 @@ def cron(): ORDER BY count DESC, type LIMIT 4;""", params)) issues_by_type = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 max_issues_by_type = sum(i["count"] for i in issues_by_type) for i in issues_by_type: i["type"] = get_issue_title(i["type"]) @@ -161,6 +171,9 @@ def cron(): GROUP BY timestamp_i ORDER BY timestamp_i;""", params)) issues_breakdown_by_day = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 for i in issues_breakdown_by_day: i["sum"] = sum(x["count"] for x in i["partition"]) for j in i["partition"]: @@ -207,6 +220,9 @@ def cron(): GROUP BY type ORDER BY issue_count DESC;""", params)) issues_breakdown_list = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 if len(issues_breakdown_list) > 4: others = {"type": "Others", "sessions_count": sum(i["sessions_count"] for i in issues_breakdown_list[4:]), diff --git a/api/chalicelib/utils/pg_client.py b/api/chalicelib/utils/pg_client.py index 2abc9f6c7..eda7747f8 100644 --- a/api/chalicelib/utils/pg_client.py +++ b/api/chalicelib/utils/pg_client.py @@ -75,9 +75,11 @@ class PostgresClient: connection = None cursor = None long_query = False + unlimited_query = False def __init__(self, long_query=False, unlimited_query=False): self.long_query = long_query + self.unlimited_query = unlimited_query if unlimited_query: long_config = dict(_PG_CONFIG) long_config["application_name"] += "-UNLIMITED" @@ -85,7 +87,7 @@ class PostgresClient: elif long_query: long_config = dict(_PG_CONFIG) long_config["application_name"] += "-LONG" - long_config["options"] = f"-c statement_timeout={config('pg_long_timeout', cast=int, default=5*60) * 1000}" + long_config["options"] = f"-c statement_timeout={config('pg_long_timeout', cast=int, default=5 * 60) * 1000}" self.connection = psycopg2.connect(**long_config) else: self.connection = postgreSQL_pool.getconn() @@ -99,11 +101,11 @@ class PostgresClient: try: self.connection.commit() self.cursor.close() - if self.long_query: + if self.long_query or self.unlimited_query: self.connection.close() except Exception as error: print("Error while committing/closing PG-connection", error) - if str(error) == "connection already closed": + if str(error) == "connection already closed" and not self.long_query and not self.unlimited_query: print("Recreating the connexion pool") make_pool() else: diff --git a/api/routers/core.py b/api/routers/core.py index df98c1c09..2c3ff5b90 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -1171,4 +1171,5 @@ def get_limits(context: schemas.CurrentContext = Depends(OR_context)): @public_app.put('/', tags=["health"]) @public_app.delete('/', tags=["health"]) def health_check(): - return {"data": f"live {config('version_number', default='')}"} + return {"data": {"stage": f"live {config('version_number', default='')}", + "internalCrons": config("LOCAL_CRONS", default=False, cast=bool)}} diff --git a/api/schemas.py b/api/schemas.py index bacceea78..314a0f7d2 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -389,7 +389,6 @@ class EventType(str, Enum): request = "REQUEST" request_details = "FETCH" graphql = "GRAPHQL" - graphql_details = "GRAPHQL_DETAILS" state_action = "STATEACTION" error = "ERROR" click_ios = "CLICK_IOS" @@ -568,9 +567,9 @@ class _SessionSearchEventRaw(__MixedSearchFilter): elif values.get("type") == EventType.request_details: assert isinstance(values.get("filters"), List) and len(values.get("filters", [])) > 0, \ f"filters should be defined for {EventType.request_details.value}" - elif values.get("type") == EventType.graphql_details: + elif values.get("type") == EventType.graphql: assert isinstance(values.get("filters"), List) and len(values.get("filters", [])) > 0, \ - f"filters should be defined for {EventType.graphql_details.value}" + f"filters should be defined for {EventType.graphql.value}" return values @@ -1032,6 +1031,8 @@ class LiveSessionSearchFilterSchema(BaseModel): value: Union[List[str], str] = Field(...) type: LiveFilterType = Field(...) source: Optional[str] = Field(None) + operator: Literal[SearchEventOperator._is.value, + SearchEventOperator._contains.value] = Field(SearchEventOperator._contains.value) @root_validator def validator(cls, values): diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index f599bfc45..2a2aef8f0 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.10-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec nodejs npm tini RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg diff --git a/ee/api/app.py b/ee/api/app.py index 505f1393c..1e12e6015 100644 --- a/ee/api/app.py +++ b/ee/api/app.py @@ -16,7 +16,7 @@ from routers.crons import core_crons from routers.crons import core_dynamic_crons from routers.subs import dashboard, insights, metrics, v1_api_ee -app = FastAPI(root_path="/api") +app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) @app.middleware('http') diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index 815d39106..ff43cca41 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -274,7 +274,6 @@ def get(user_id, tenant_id): (CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member, - api_key, origin, role_id, roles.name AS role_name, diff --git a/ee/api/chalicelib/core/weekly_report.py b/ee/api/chalicelib/core/weekly_report.py index 90256d795..96bda859e 100644 --- a/ee/api/chalicelib/core/weekly_report.py +++ b/ee/api/chalicelib/core/weekly_report.py @@ -32,7 +32,8 @@ def cron(): if not helper.has_smtp(): print("!!! No SMTP configuration found, ignoring weekly report") return - with pg_client.PostgresClient(long_query=True) as cur: + _now = TimeUTC.now() + with pg_client.PostgresClient(unlimited_query=True) as cur: params = {"tomorrow": TimeUTC.midnight(delta_days=1), "3_days_ago": TimeUTC.midnight(delta_days=-3), "1_week_ago": TimeUTC.midnight(delta_days=-7), @@ -87,6 +88,9 @@ def cron(): AND issues.timestamp >= %(5_week_ago)s ) AS month_1_issues ON (TRUE);"""), params) projects_data = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report query: {_now2 - _now} ms") + _now = _now2 emails_to_send = [] for p in projects_data: params["project_id"] = p["project_id"] @@ -117,6 +121,9 @@ def cron(): ) AS timestamp_i ORDER BY timestamp_i;""", params)) days_partition = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 max_days_partition = max(x['issues_count'] for x in days_partition) for d in days_partition: if max_days_partition <= 0: @@ -133,6 +140,9 @@ def cron(): ORDER BY count DESC, type LIMIT 4;""", params)) issues_by_type = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 max_issues_by_type = sum(i["count"] for i in issues_by_type) for i in issues_by_type: i["type"] = get_issue_title(i["type"]) @@ -162,6 +172,9 @@ def cron(): GROUP BY timestamp_i ORDER BY timestamp_i;""", params)) issues_breakdown_by_day = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 for i in issues_breakdown_by_day: i["sum"] = sum(x["count"] for x in i["partition"]) for j in i["partition"]: @@ -208,6 +221,9 @@ def cron(): GROUP BY type ORDER BY issue_count DESC;""", params)) issues_breakdown_list = cur.fetchall() + _now2 = TimeUTC.now() + print(f">> Weekly report s-query-1: {_now2 - _now} ms project_id: {p['project_id']}") + _now = _now2 if len(issues_breakdown_list) > 4: others = {"type": "Others", "sessions_count": sum(i["sessions_count"] for i in issues_breakdown_list[4:]), diff --git a/ee/api/entrypoint.sh b/ee/api/entrypoint.sh index 60aba72a6..724c7d6f1 100755 --- a/ee/api/entrypoint.sh +++ b/ee/api/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/sh sh env_vars.sh -source .env.override +source /tmp/.env.override cd sourcemap-reader nohup npm start &> /tmp/sourcemap-reader.log & cd .. diff --git a/ee/api/entrypoint_alerts.sh b/ee/api/entrypoint_alerts.sh index d2e6d677b..04e60ce1b 100755 --- a/ee/api/entrypoint_alerts.sh +++ b/ee/api/entrypoint_alerts.sh @@ -1,4 +1,4 @@ #!/bin/sh sh env_vars.sh -source .env.override +source /tmp/.env.override uvicorn app:app --host 0.0.0.0 --reload diff --git a/ee/api/entrypoint_crons.sh b/ee/api/entrypoint_crons.sh index ef29496b2..b6c03bf75 100755 --- a/ee/api/entrypoint_crons.sh +++ b/ee/api/entrypoint_crons.sh @@ -1,4 +1,4 @@ #!/bin/sh sh env_vars.sh -source .env.override +source /tmp/.env.override python app_crons.py $ACTION diff --git a/ee/api/env_vars.sh b/ee/api/env_vars.sh index a17610aab..22db81961 100755 --- a/ee/api/env_vars.sh +++ b/ee/api/env_vars.sh @@ -1,12 +1,12 @@ #!/bin/sh -touch .env.override +touch /tmp/.env.override if [[ -z "${ENV_CONFIG_OVERRIDE_PATH}" ]]; then echo 'no env-override' else override=$ENV_CONFIG_OVERRIDE_PATH if [ -f "$override" ]; then - cp $override .env.override + cp $override /tmp/.env.override else echo "$override does not exist." fi diff --git a/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql b/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql index 1ea5c6ab6..cdf316fa4 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/1.7.0/1.7.0.sql @@ -56,6 +56,7 @@ ALTER TABLE IF EXISTS events.resources PRIMARY KEY (session_id, message_id, timestamp); COMMIT; +CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS autocomplete_unique_project_id_md5value_type_idx ON autocomplete (project_id, md5(value), type); CREATE INDEX CONCURRENTLY IF NOT EXISTS projects_tenant_id_idx ON public.projects (tenant_id); CREATE INDEX CONCURRENTLY IF NOT EXISTS projects_project_id_deleted_at_n_idx ON public.projects (project_id) WHERE deleted_at IS NULL; ALTER TYPE metric_type ADD VALUE IF NOT EXISTS 'funnel'; @@ -211,4 +212,5 @@ $$ END IF; END $$; +DROP INDEX IF EXISTS autocomplete_unique; COMMIT; \ No newline at end of file diff --git a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql index 2d1c2b95f..e236ce90e 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -658,7 +658,7 @@ $$ project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE ); - CREATE unique index IF NOT EXISTS autocomplete_unique ON autocomplete (project_id, value, type); + CREATE UNIQUE INDEX IF NOT EXISTS autocomplete_unique_project_id_md5value_type_idx ON autocomplete (project_id, md5(value), type); CREATE index IF NOT EXISTS autocomplete_project_id_idx ON autocomplete (project_id); CREATE INDEX IF NOT EXISTS autocomplete_type_idx ON public.autocomplete (type); diff --git a/ee/utilities/package-lock.json b/ee/utilities/package-lock.json index 8aa6c9196..6b9dbdf1c 100644 --- a/ee/utilities/package-lock.json +++ b/ee/utilities/package-lock.json @@ -112,9 +112,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "node_modules/@types/node": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", - "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "node_modules/accepts": { "version": "1.3.8", @@ -1179,9 +1179,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "@types/node": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", - "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "accepts": { "version": "1.3.8", diff --git a/ee/utilities/utils/helper-ee.js b/ee/utilities/utils/helper-ee.js index 50b414b7a..86997f0c4 100644 --- a/ee/utilities/utils/helper-ee.js +++ b/ee/utilities/utils/helper-ee.js @@ -91,6 +91,7 @@ const extractPayloadFromRequest = async function (req, res) { return helper.extractPayloadFromRequest(req); } filters.filter = helper.objectToObjectOfArrays(filters.filter); + filters.filter = helper.transformFilters(filters.filter); debug && console.log("payload/filters:" + JSON.stringify(filters)) return Object.keys(filters).length > 0 ? filters : undefined; } diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index ed0af5625..dc8b54025 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -24,7 +24,7 @@ function SessionList(props: Props) { return (
-
+
{props.userId}'s Live Sessions{' '} diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index 46a200192..a2901812b 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -10,7 +10,7 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; // const ALL = 'all'; const PER_PAGE = 10; -const AUTOREFRESH_INTERVAL = 3 * 60 * 1000; +const AUTOREFRESH_INTERVAL = 5 * 60 * 1000; var timeoutId; @connect(state => ({ diff --git a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js index bf4bf8b55..606ecaf67 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js +++ b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js @@ -4,7 +4,10 @@ import SortDropdown from '../Filters/SortDropdown'; import { numberWithCommas } from 'App/utils'; import SelectDateRange from 'Shared/SelectDateRange'; import { applyFilter } from 'Duck/search'; -import Period from 'Types/app/period'; +import Record from 'Types/app/period'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import { moment } from 'App/dateRange'; const sortOptionsMap = { 'startTs-desc': 'Newest', @@ -15,13 +18,32 @@ const sortOptionsMap = { const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); function SessionListHeader({ activeTab, count, applyFilter, filter }) { + const { settingsStore } = useStore(); + + const label = useObserver(() => settingsStore.sessionSettings.timezone.label); + const getTimeZoneOffset = React.useCallback(() => { + return label.slice(-6); + }, [label]); + const { startDate, endDate, rangeValue } = filter; - const period = new Period({ start: startDate, end: endDate, rangeName: rangeValue }); + const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue, timezoneOffset: getTimeZoneOffset() }); const onDateChange = (e) => { const dateValues = e.toJSON(); + dateValues.startDate = moment(dateValues.startDate).utcOffset(getTimeZoneOffset(), true).valueOf(); + dateValues.endDate = moment(dateValues.endDate).utcOffset(getTimeZoneOffset(), true).valueOf(); applyFilter(dateValues); }; + + React.useEffect(() => { + if (label) { + const dateValues = period.toJSON(); + dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + applyFilter(dateValues); + } + }, [label]); + return (
@@ -32,7 +54,7 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { {
Sessions Captured in - +
}
diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js index a26576fc3..aeb28fe31 100644 --- a/frontend/app/components/Client/Integrations/IntegrationForm.js +++ b/frontend/app/components/Client/Integrations/IntegrationForm.js @@ -35,9 +35,9 @@ export default class IntegrationForm extends React.PureComponent { onChangeSelect = ({ value }) => { const { sites, list, name } = this.props; - const site = sites.find(s => s.id === value); + const site = sites.find(s => s.id === value.value); this.setState({ currentSiteId: site.id }) - this.init(value); + this.init(value.value); } init = (siteId) => { diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx index d2df0431d..477e42d8f 100644 --- a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -153,6 +153,7 @@ function UserForm(props: Props) { )); } -export default connect(state => ({ +export default connect((state: any) => ({ isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', + isSmtp: state.getIn([ 'user', 'account', 'smtp' ]), }))(UserForm); \ No newline at end of file diff --git a/frontend/app/components/Errors/Error/DistributionBar.js b/frontend/app/components/Errors/Error/DistributionBar.js index 6df611d0a..e6cc38ca5 100644 --- a/frontend/app/components/Errors/Error/DistributionBar.js +++ b/frontend/app/components/Errors/Error/DistributionBar.js @@ -6,52 +6,55 @@ import cls from './distributionBar.module.css'; import { colorScale } from 'App/utils'; function DistributionBar({ className, title, partitions }) { - if (partitions.length === 0) { - return null; - } + if (partitions.length === 0) { + return null; + } - const values = Array(partitions.length).fill().map((element, index) => index + 0); - const colors = colorScale(values, Styles.colors); + const values = Array(partitions.length) + .fill() + .map((element, index) => index + 0); + const colors = colorScale(values, Styles.colors); - return ( -
-
-
{ title }
-
-
- -
-
{ `${ Math.round(partitions[0].prc) }% ` }
-
-
-
- { partitions.map((p, index) => - - { p.label }
- {`${ Math.round(p.prc) }%`} -
- } - className="w-full" - > -
- - )} -
-
- ); + return ( +
+
+
{title}
+
+
+ +
+
{`${Math.round(partitions[0].prc)}% `}
+
+
+
+ {partitions.map((p, index) => ( + + {p.label} +
+ {`${Math.round(p.prc)}%`} +
+ } + style={{ + marginLeft: '1px', + width: `${p.prc}%`, + backgroundColor: colors(index), + }} + > +
+ + ))} +
+
+ ); } -DistributionBar.displayName = "DistributionBar"; -export default DistributionBar; \ No newline at end of file +DistributionBar.displayName = 'DistributionBar'; +export default DistributionBar; diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 9e6efa0de..9726e83ee 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -64,7 +64,7 @@ const Header = (props) => { }, [siteId]) return ( -
+
diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js index a89904907..d999397f1 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js @@ -4,7 +4,7 @@ import stl from './installDocs.module.css' import cn from 'classnames' import Highlight from 'react-highlight' import CircleNumber from '../../CircleNumber' -import { Slider, CopyButton } from 'UI' +import { CopyButton } from 'UI' import { Toggler } from 'UI'; const installationCommand = 'npm i @openreplay/tracker' @@ -30,8 +30,7 @@ function MyApp() { //... }` -function InstallDocs({ siteId, sites }) { - const site = sites.find(s => s.id === siteId); +function InstallDocs({ site }) { const _usageCode = usageCode.replace('PROJECT_KEY', site.projectKey) const _usageCodeSST = usageCodeSST.replace('PROJECT_KEY', site.projectKey) const [isSpa, setIsSpa] = useState(true) @@ -98,9 +97,6 @@ function InstallDocs({ siteId, sites }) { ) } -// export default InstallDocs - export default connect(state => ({ - siteId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), + site: state.getIn([ 'site', 'instance' ]), }))(InstallDocs) \ No newline at end of file diff --git a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx index cec3e1af2..03d74a247 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Dropdown, Loader, Icon } from 'UI'; -import DateRange from 'Shared/DateRange'; +import { Loader, Icon } from 'UI'; import { connect } from 'react-redux'; import { fetchInsights } from 'Duck/sessions'; import SelectorsList from './components/SelectorsList/SelectorsList'; @@ -11,100 +10,103 @@ import Period from 'Types/app/period'; const JUMP_OFFSET = 1000; interface Props { - filters: any - fetchInsights: (filters: Record) => void - urls: [] - insights: any - events: Array - urlOptions: Array - loading: boolean - host: string - setActiveTab: (tab: string) => void + filters: any; + fetchInsights: (filters: Record) => void; + urls: []; + insights: any; + events: Array; + urlOptions: Array; + loading: boolean; + host: string; + setActiveTab: (tab: string) => void; } -function PageInsightsPanel({ - filters, fetchInsights, events = [], insights, urlOptions, host, loading = true, setActiveTab -}: Props) { - const [insightsFilters, setInsightsFilters] = useState(filters) - const defaultValue = (urlOptions && urlOptions[0]) ? urlOptions[0].value : '' +function PageInsightsPanel({ filters, fetchInsights, events = [], insights, urlOptions, host, loading = true, setActiveTab }: Props) { + const [insightsFilters, setInsightsFilters] = useState(filters); + const defaultValue = urlOptions && urlOptions[0] ? urlOptions[0].value : ''; - const period = new Period({ - start: insightsFilters.startDate, - end: insightsFilters.endDate, - rangeName: insightsFilters.rangeValue - }); + const period = Period({ + start: insightsFilters.startDate, + end: insightsFilters.endDate, + rangeName: insightsFilters.rangeValue, + }); - const onDateChange = (e) => { - const { startDate, endDate, rangeValue } = e.toJSON(); - setInsightsFilters({ ...insightsFilters, startDate, endDate, rangeValue }) - } + const onDateChange = (e: any) => { + const { startDate, endDate, rangeValue } = e.toJSON(); + setInsightsFilters({ ...insightsFilters, startDate, endDate, rangeValue }); + }; - useEffect(() => { - markTargets(insights.toJS()); - return () => { - markTargets(null) - } - }, [insights]) + useEffect(() => { + markTargets(insights.toJS()); + return () => { + markTargets(null); + }; + }, [insights]); - useEffect(() => { - if (urlOptions && urlOptions[0]) { - const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; - Player.pause(); - fetchInsights({ ...insightsFilters, url }) - } - }, [insightsFilters]) + useEffect(() => { + if (urlOptions && urlOptions[0]) { + const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; + Player.pause(); + fetchInsights({ ...insightsFilters, url }); + } + }, [insightsFilters]); - const onPageSelect = ({ value }: { value: Array }) => { - const event = events.find(item => item.url === value) - Player.jump(event.time + JUMP_OFFSET) - setInsightsFilters({ ...insightsFilters, url: host + value }) - markTargets([]) - }; + const onPageSelect = ({ value }: any) => { + const event = events.find((item) => item.url === value.value); + Player.jump(event.time + JUMP_OFFSET); + setInsightsFilters({ ...insightsFilters, url: host + value.value }); + markTargets([]); + }; - return ( -
-
-
- Clicks - + return ( +
+
+
+ Clicks + +
+
{ + setActiveTab(''); + }} + className="ml-auto flex items-center justify-center bg-white cursor-pointer" + > + +
+
+
+
In Page
+ -
- - - -
- ) + ); } -export default connect(state => { - const events = state.getIn([ 'sessions', 'visitedEvents' ]) - return { - filters: state.getIn(['sessions', 'insightFilters']), - host: state.getIn([ 'sessions', 'host' ]), - insights: state.getIn([ 'sessions', 'insights' ]), - events: events, - urlOptions: events.map(({ url, host }: any) => ({ label: url, value: url, host })), - loading: state.getIn([ 'sessions', 'fetchInsightsRequest', 'loading' ]), - } -}, { fetchInsights })(PageInsightsPanel); +export default connect( + (state) => { + const events = state.getIn(['sessions', 'visitedEvents']); + return { + filters: state.getIn(['sessions', 'insightFilters']), + host: state.getIn(['sessions', 'host']), + insights: state.getIn(['sessions', 'insights']), + events: events, + urlOptions: events.map(({ url, host }: any) => ({ label: url, value: url, host })), + loading: state.getIn(['sessions', 'fetchInsightsRequest', 'loading']), + }; + }, + { fetchInsights } +)(PageInsightsPanel); diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx index 9007b6684..9a3ecc210 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx @@ -1,30 +1,34 @@ -import React, { useState } from 'react' -import stl from './SelectorCard.module.css' +import React, { useState } from 'react'; +import stl from './SelectorCard.module.css'; import cn from 'classnames'; import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; import { activeTarget } from 'Player'; import { Tooltip } from 'react-tippy'; interface Props { - index?: number, - target: MarkedTarget, - showContent: boolean + index?: number; + target: MarkedTarget; + showContent: boolean; } -export default function SelectorCard({ index = 1, target, showContent } : Props) { - return ( -
activeTarget(index)}> -
- {/* @ts-ignore */} -
{index + 1}
-
{target.selector}
-
- { showContent && ( -
-
{target.count} Clicks - {target.percent}%
-
TOTAL CLICKS
+export default function SelectorCard({ index = 1, target, showContent }: Props) { + return ( +
activeTarget(index)}> +
+ {/* @ts-ignore */} + +
{index + 1}
+
+
{target.selector}
+
+ {showContent && ( +
+
+ {target.count} Clicks - {target.percent}% +
+
TOTAL CLICKS
+
+ )}
- ) } -
- ) + ); } diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx index aceefb3b7..86274baba 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx @@ -1,33 +1,26 @@ -import React, { useState } from 'react' -import { NoContent } from 'UI' +import React from 'react'; +import { NoContent } from 'UI'; import { connectPlayer } from 'Player/store'; import SelectorCard from '../SelectorCard/SelectorCard'; import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; -import stl from './selectorList.module.css' +import stl from './selectorList.module.css'; interface Props { - targets: Array, - activeTargetIndex: number + targets: Array; + activeTargetIndex: number; } -function SelectorsList({ targets, activeTargetIndex }: Props) { - return ( - -
- { targets && targets.map((target, index) => ( - - ))} -
-
- ) +function SelectorsList({ targets, activeTargetIndex }: Props) { + return ( + +
+ {targets && targets.map((target, index) => )} +
+
+ ); } - -export default connectPlayer(state => ({ - targets: state.markedTargets, - activeTargetIndex: state.activeTargetIndex, -}))(SelectorsList) +export default connectPlayer((state: any) => ({ + targets: state.markedTargets, + activeTargetIndex: state.activeTargetIndex, +}))(SelectorsList); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 154e862a7..3d82dae7c 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -10,7 +10,7 @@ import SubFilterItem from '../SubFilterItem'; interface Props { filterIndex: number; filter: any; // event/filter - onUpdate: (filter) => void; + onUpdate: (filter: any) => void; onRemoveFilter: () => void; isFilter?: boolean; saveRequestPayloads?: boolean; @@ -20,26 +20,26 @@ function FilterItem(props: Props) { const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined'); const isSubFilter = filter.type === FilterType.SUB_FILTERS; - const replaceFilter = (filter) => { + const replaceFilter = (filter: any) => { props.onUpdate({ ...filter, value: [''], - filters: filter.filters ? filter.filters.map((i) => ({ ...i, value: [''] })) : [], + filters: filter.filters ? filter.filters.map((i: any) => ({ ...i, value: [''] })) : [], }); }; - const onOperatorChange = (e, { name, value }) => { + const onOperatorChange = (e: any, { name, value }: any) => { props.onUpdate({ ...filter, operator: value.value }); }; - const onSourceOperatorChange = (e, { name, value }) => { + const onSourceOperatorChange = (e: any, { name, value }: any) => { props.onUpdate({ ...filter, sourceOperator: value.value }); }; - const onUpdateSubFilter = (subFilter, subFilterIndex) => { + const onUpdateSubFilter = (subFilter: any, subFilterIndex: any) => { props.onUpdate({ ...filter, - filters: filter.filters.map((i, index) => { + filters: filter.filters.map((i: any, index: any) => { if (index === subFilterIndex) { return subFilter; } @@ -90,8 +90,8 @@ function FilterItem(props: Props) { {isSubFilter && (
{filter.filters - .filter((i) => (i.key !== FilterKey.FETCH_REQUEST_BODY && i.key !== FilterKey.FETCH_RESPONSE_BODY) || saveRequestPayloads) - .map((subFilter, subFilterIndex) => ( + .filter((i: any) => (i.key !== FilterKey.FETCH_REQUEST_BODY && i.key !== FilterKey.FETCH_RESPONSE_BODY) || saveRequestPayloads) + .map((subFilter: any, subFilterIndex: any) => ( void; + filter: any; + onUpdate: (filter: any) => void; } function FilterSource(props: Props) { - const { filter } = props; - const [value, setValue] = useState(filter.source[0] || ''); + const { filter } = props; + const [value, setValue] = useState(filter.source[0] || ''); + const debounceUpdate: any = React.useCallback(debounce(props.onUpdate, 1000), [props.onUpdate]); - const onChange = ({ target: { value, name } }) => { - props.onUpdate({ ...filter, [name]: [value] }) - } + useEffect(() => { + setValue(filter.source[0] || ''); + }, [filter]); - useEffect(() => { - setValue(filter.source[0] || ''); - }, [filter]) + useEffect(() => { + debounceUpdate({ ...filter, source: [value] }); + }, [value]); - useEffect(() => { - props.onUpdate({ ...filter, source: [value] }) - }, [value]) + const write = ({ target: { value, name } }: any) => setValue(value); - const write = ({ target: { value, name } }) => setValue(value) + const renderFiled = () => { + switch (filter.sourceType) { + case FilterType.NUMBER: + return ( +
+ +
{filter.sourceUnit}
+
+ ); + } + }; - const renderFiled = () => { - switch(filter.sourceType) { - case FilterType.NUMBER: - return ( - - ) - } - } - - return ( -
- { renderFiled()} -
- ); + return
{renderFiled()}
; } -export default FilterSource; \ No newline at end of file +export default FilterSource; diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 29dce323d..5638f9a1d 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -6,6 +6,7 @@ import FilterValueDropdown from '../FilterValueDropdown'; import FilterDuration from '../FilterDuration'; import { debounce } from 'App/utils'; import { assist as assistRoute, isRoute } from 'App/routes'; +import cn from 'classnames'; const ASSIST_ROUTE = assistRoute(); @@ -172,7 +173,8 @@ function FilterValue(props: Props) { }; return ( -
+ // +
{filter.type === FilterType.DURATION ? renderValueFiled(filter.value, 0) : filter.value && diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 12b6ba016..7eaabc252 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -6,17 +6,19 @@ import { components } from 'react-select'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; interface Props { period: any; onChange: (data: any) => void; disableCustom?: boolean; right?: boolean; + timezone?: string; [x: string]: any; } function SelectDateRange(props: Props) { const [isCustom, setIsCustom] = React.useState(false); - const { right = false, period, disableCustom = false, ...rest } = props; + const { right = false, period, disableCustom = false, timezone, ...rest } = props; let selectedValue = DATE_RANGE_OPTIONS.find((obj: any) => obj.value === period.rangeName); const options = DATE_RANGE_OPTIONS.filter((obj: any) => (disableCustom ? obj.value !== CUSTOM_RANGE : true)); @@ -24,15 +26,20 @@ function SelectDateRange(props: Props) { if (value === CUSTOM_RANGE) { setIsCustom(true); } else { + // @ts-ignore props.onChange(new Period({ rangeName: value })); } }; const onApplyDateRange = (value: any) => { - props.onChange(new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end })); + // @ts-ignore + const range = new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end }) + props.onChange(range); setIsCustom(false); }; + const isCustomRange = period.rangeName === CUSTOM_RANGE; + const customRange = isCustomRange ? period.rangeFormatted() : ''; return (