Merge remote-tracking branch 'origin/dev' into api_insights
# Conflicts: # scripts/helm/db/init_dbs/postgresql/init_schema.sql
This commit is contained in:
commit
ec0bb60443
27 changed files with 1275 additions and 1125 deletions
|
|
@ -897,3 +897,8 @@ def sessions_live_search(projectId, context):
|
||||||
def get_heatmaps_by_url(projectId, context):
|
def get_heatmaps_by_url(projectId, context):
|
||||||
data = app.current_request.json_body
|
data = app.current_request.json_body
|
||||||
return {"data": heatmaps.get_by_url(project_id=projectId, data=data)}
|
return {"data": heatmaps.get_by_url(project_id=projectId, data=data)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/general_stats', methods=['GET'], authorizer=None)
|
||||||
|
def get_general_stats():
|
||||||
|
return {"data": {"sessions:": sessions.count_all()}}
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ def search2_pg(data, project_id, user_id, favorite_only=False, errors_only=False
|
||||||
"projectId": project_id,
|
"projectId": project_id,
|
||||||
"userId": user_id}
|
"userId": user_id}
|
||||||
with pg_client.PostgresClient() as cur:
|
with pg_client.PostgresClient() as cur:
|
||||||
|
ss_constraints = []
|
||||||
extra_constraints = [
|
extra_constraints = [
|
||||||
cur.mogrify("s.project_id = %(project_id)s", {"project_id": project_id}),
|
cur.mogrify("s.project_id = %(project_id)s", {"project_id": project_id}),
|
||||||
cur.mogrify("s.duration IS NOT NULL", {})
|
cur.mogrify("s.duration IS NOT NULL", {})
|
||||||
|
|
@ -173,7 +174,96 @@ def search2_pg(data, project_id, user_id, favorite_only=False, errors_only=False
|
||||||
extra_constraints.append(cur.mogrify("fs.user_id = %(userId)s", {"userId": user_id}))
|
extra_constraints.append(cur.mogrify("fs.user_id = %(userId)s", {"userId": user_id}))
|
||||||
events_query_part = ""
|
events_query_part = ""
|
||||||
|
|
||||||
|
if "filters" in data:
|
||||||
|
meta_keys = metadata.get(project_id=project_id)
|
||||||
|
meta_keys = {m["key"]: m["index"] for m in meta_keys}
|
||||||
|
for f in data["filters"]:
|
||||||
|
if not isinstance(f.get("value"), list):
|
||||||
|
f["value"] = [f.get("value")]
|
||||||
|
if len(f["value"]) == 0 or f["value"][0] is None:
|
||||||
|
continue
|
||||||
|
filter_type = f["type"].upper()
|
||||||
|
f["value"] = __get_sql_value_multiple(f["value"])
|
||||||
|
if filter_type == sessions_metas.meta_type.USERBROWSER:
|
||||||
|
op = __get_sql_operator_multiple(f["operator"])
|
||||||
|
extra_constraints.append(cur.mogrify(f's.user_browser {op} %(value)s', {"value": f["value"]}))
|
||||||
|
ss_constraints.append(cur.mogrify(f'ms.user_browser {op} %(value)s', {"value": f["value"]}))
|
||||||
|
|
||||||
|
elif filter_type in [sessions_metas.meta_type.USEROS, sessions_metas.meta_type.USEROS_IOS]:
|
||||||
|
op = __get_sql_operator_multiple(f["operator"])
|
||||||
|
extra_constraints.append(cur.mogrify(f's.user_os {op} %(value)s', {"value": f["value"]}))
|
||||||
|
ss_constraints.append(cur.mogrify(f'ms.user_os {op} %(value)s', {"value": f["value"]}))
|
||||||
|
|
||||||
|
elif filter_type in [sessions_metas.meta_type.USERDEVICE, sessions_metas.meta_type.USERDEVICE_IOS]:
|
||||||
|
op = __get_sql_operator_multiple(f["operator"])
|
||||||
|
extra_constraints.append(cur.mogrify(f's.user_device {op} %(value)s', {"value": f["value"]}))
|
||||||
|
ss_constraints.append(cur.mogrify(f'ms.user_device {op} %(value)s', {"value": f["value"]}))
|
||||||
|
|
||||||
|
elif filter_type in [sessions_metas.meta_type.USERCOUNTRY, sessions_metas.meta_type.USERCOUNTRY_IOS]:
|
||||||
|
op = __get_sql_operator_multiple(f["operator"])
|
||||||
|
extra_constraints.append(cur.mogrify(f's.user_country {op} %(value)s', {"value": f["value"]}))
|
||||||
|
ss_constraints.append(cur.mogrify(f'ms.user_country {op} %(value)s', {"value": f["value"]}))
|
||||||
|
elif filter_type == "duration".upper():
|
||||||
|
if len(f["value"]) > 0 and f["value"][0] is not None:
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify("s.duration >= %(minDuration)s", {"minDuration": f["value"][0]}))
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify("ms.duration >= %(minDuration)s", {"minDuration": f["value"][0]}))
|
||||||
|
if len(f["value"]) > 1 and f["value"][1] is not None and f["value"][1] > 0:
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify("s.duration <= %(maxDuration)s", {"maxDuration": f["value"][1]}))
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify("ms.duration <= %(maxDuration)s", {"maxDuration": f["value"][1]}))
|
||||||
|
elif filter_type == sessions_metas.meta_type.REFERRER:
|
||||||
|
# events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)"
|
||||||
|
extra_from += f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"
|
||||||
|
op = __get_sql_operator_multiple(f["operator"])
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify(f"p.base_referrer {op} %(referrer)s", {"referrer": f["value"]}))
|
||||||
|
elif filter_type == events.event_type.METADATA.ui_type:
|
||||||
|
op = __get_sql_operator(f["operator"])
|
||||||
|
if f.get("key") in meta_keys.keys():
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify(f"s.{metadata.index_to_colname(meta_keys[f['key']])} {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)}))
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify(f"ms.{metadata.index_to_colname(meta_keys[f['key']])} {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)}))
|
||||||
|
elif filter_type in [sessions_metas.meta_type.USERID, sessions_metas.meta_type.USERID_IOS]:
|
||||||
|
op = __get_sql_operator(f["operator"])
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify(f"s.user_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify(f"ms.user_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
elif filter_type in [sessions_metas.meta_type.USERANONYMOUSID,
|
||||||
|
sessions_metas.meta_type.USERANONYMOUSID_IOS]:
|
||||||
|
op = __get_sql_operator(f["operator"])
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify(f"s.user_anonymous_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify(f"ms.user_anonymous_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
elif filter_type in [sessions_metas.meta_type.REVID, sessions_metas.meta_type.REVID_IOS]:
|
||||||
|
op = __get_sql_operator(f["operator"])
|
||||||
|
extra_constraints.append(
|
||||||
|
cur.mogrify(f"s.rev_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
ss_constraints.append(
|
||||||
|
cur.mogrify(f"ms.rev_id {op} %(value)s",
|
||||||
|
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
if len(data.get("events", [])) > 0:
|
if len(data.get("events", [])) > 0:
|
||||||
|
ss_constraints = [s.decode('UTF-8') for s in ss_constraints]
|
||||||
events_query_from = []
|
events_query_from = []
|
||||||
event_index = 0
|
event_index = 0
|
||||||
|
|
||||||
|
|
@ -268,7 +358,8 @@ def search2_pg(data, project_id, user_id, favorite_only=False, errors_only=False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
if event_index == 0:
|
||||||
|
event_where += ss_constraints
|
||||||
if is_not:
|
if is_not:
|
||||||
if event_index == 0:
|
if event_index == 0:
|
||||||
events_query_from.append(cur.mogrify(f"""\
|
events_query_from.append(cur.mogrify(f"""\
|
||||||
|
|
@ -316,73 +407,6 @@ def search2_pg(data, project_id, user_id, favorite_only=False, errors_only=False
|
||||||
else:
|
else:
|
||||||
data["events"] = []
|
data["events"] = []
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
if "filters" in data:
|
|
||||||
meta_keys = metadata.get(project_id=project_id)
|
|
||||||
meta_keys = {m["key"]: m["index"] for m in meta_keys}
|
|
||||||
for f in data["filters"]:
|
|
||||||
if not isinstance(f.get("value"), list):
|
|
||||||
f["value"] = [f.get("value")]
|
|
||||||
if len(f["value"]) == 0 or f["value"][0] is None:
|
|
||||||
continue
|
|
||||||
filter_type = f["type"].upper()
|
|
||||||
f["value"] = __get_sql_value_multiple(f["value"])
|
|
||||||
if filter_type == sessions_metas.meta_type.USERBROWSER:
|
|
||||||
op = __get_sql_operator_multiple(f["operator"])
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f's.user_browser {op} %(value)s', {"value": f["value"]}))
|
|
||||||
|
|
||||||
elif filter_type in [sessions_metas.meta_type.USEROS, sessions_metas.meta_type.USEROS_IOS]:
|
|
||||||
op = __get_sql_operator_multiple(f["operator"])
|
|
||||||
extra_constraints.append(cur.mogrify(f's.user_os {op} %(value)s', {"value": f["value"]}))
|
|
||||||
|
|
||||||
elif filter_type in [sessions_metas.meta_type.USERDEVICE, sessions_metas.meta_type.USERDEVICE_IOS]:
|
|
||||||
op = __get_sql_operator_multiple(f["operator"])
|
|
||||||
extra_constraints.append(cur.mogrify(f's.user_device {op} %(value)s', {"value": f["value"]}))
|
|
||||||
|
|
||||||
elif filter_type in [sessions_metas.meta_type.USERCOUNTRY, sessions_metas.meta_type.USERCOUNTRY_IOS]:
|
|
||||||
op = __get_sql_operator_multiple(f["operator"])
|
|
||||||
extra_constraints.append(cur.mogrify(f's.user_country {op} %(value)s', {"value": f["value"]}))
|
|
||||||
elif filter_type == "duration".upper():
|
|
||||||
if len(f["value"]) > 0 and f["value"][0] is not None:
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify("s.duration >= %(minDuration)s", {"minDuration": f["value"][0]}))
|
|
||||||
if len(f["value"]) > 1 and f["value"][1] is not None and f["value"][1] > 0:
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify("s.duration <= %(maxDuration)s", {"maxDuration": f["value"][1]}))
|
|
||||||
elif filter_type == sessions_metas.meta_type.REFERRER:
|
|
||||||
# events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)"
|
|
||||||
extra_from += f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"
|
|
||||||
op = __get_sql_operator_multiple(f["operator"])
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f"p.base_referrer {op} %(referrer)s", {"referrer": f["value"]}))
|
|
||||||
elif filter_type == events.event_type.METADATA.ui_type:
|
|
||||||
op = __get_sql_operator(f["operator"])
|
|
||||||
if f.get("key") in meta_keys.keys():
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f"s.{metadata.index_to_colname(meta_keys[f['key']])} {op} %(value)s",
|
|
||||||
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
|
||||||
)
|
|
||||||
elif filter_type in [sessions_metas.meta_type.USERID, sessions_metas.meta_type.USERID_IOS]:
|
|
||||||
op = __get_sql_operator(f["operator"])
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f"s.user_id {op} %(value)s",
|
|
||||||
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
|
||||||
)
|
|
||||||
elif filter_type in [sessions_metas.meta_type.USERANONYMOUSID,
|
|
||||||
sessions_metas.meta_type.USERANONYMOUSID_IOS]:
|
|
||||||
op = __get_sql_operator(f["operator"])
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f"s.user_anonymous_id {op} %(value)s",
|
|
||||||
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
|
||||||
)
|
|
||||||
elif filter_type in [sessions_metas.meta_type.REVID, sessions_metas.meta_type.REVID_IOS]:
|
|
||||||
op = __get_sql_operator(f["operator"])
|
|
||||||
extra_constraints.append(
|
|
||||||
cur.mogrify(f"s.rev_id {op} %(value)s",
|
|
||||||
{"value": helper.string_to_sql_like_with_op(f["value"][0], op)})
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
if data.get("startDate") is not None:
|
if data.get("startDate") is not None:
|
||||||
|
|
@ -741,3 +765,9 @@ def delete_sessions_by_user_ids(project_id, user_ids):
|
||||||
cur.execute(query=query)
|
cur.execute(query=query)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def count_all():
|
||||||
|
with pg_client.PostgresClient() as cur:
|
||||||
|
row = cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions")
|
||||||
|
return row.get("count", 0)
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import (
|
||||||
gzip "github.com/klauspost/pgzip"
|
gzip "github.com/klauspost/pgzip"
|
||||||
|
|
||||||
"openreplay/backend/pkg/db/postgres"
|
"openreplay/backend/pkg/db/postgres"
|
||||||
. "openreplay/backend/pkg/messages"
|
|
||||||
"openreplay/backend/pkg/token"
|
"openreplay/backend/pkg/token"
|
||||||
|
. "openreplay/backend/pkg/messages"
|
||||||
)
|
)
|
||||||
|
|
||||||
const JSON_SIZE_LIMIT int64 = 1e3 // 1Kb
|
const JSON_SIZE_LIMIT int64 = 1e3 // 1Kb
|
||||||
|
|
@ -124,17 +124,22 @@ func pushMessages(w http.ResponseWriter, r *http.Request, sessionID uint64) {
|
||||||
body := http.MaxBytesReader(w, r.Body, BEACON_SIZE_LIMIT)
|
body := http.MaxBytesReader(w, r.Body, BEACON_SIZE_LIMIT)
|
||||||
//defer body.Close()
|
//defer body.Close()
|
||||||
var reader io.ReadCloser
|
var reader io.ReadCloser
|
||||||
|
var err error
|
||||||
switch r.Header.Get("Content-Encoding") {
|
switch r.Header.Get("Content-Encoding") {
|
||||||
case "gzip":
|
case "gzip":
|
||||||
reader, err := gzip.NewReader(body)
|
log.Println("Gzip", reader)
|
||||||
|
|
||||||
|
reader, err = gzip.NewReader(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseWithError(w, http.StatusInternalServerError, err) // TODO: stage-dependent responce
|
responseWithError(w, http.StatusInternalServerError, err) // TODO: stage-dependent responce
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Println("Gzip reader init", reader)
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
default:
|
default:
|
||||||
reader = body
|
reader = body
|
||||||
}
|
}
|
||||||
|
log.Println("Reader after switch:", reader)
|
||||||
buf, err := ioutil.ReadAll(reader)
|
buf, err := ioutil.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
||||||
|
|
|
||||||
|
|
@ -1,145 +1,180 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// const FILES_SIZE_LIMIT int64 = 1e8 // 100Mb
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
// func startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) {
|
"openreplay/backend/pkg/db/postgres"
|
||||||
// type request struct {
|
"openreplay/backend/pkg/token"
|
||||||
// // SessionID *string
|
. "openreplay/backend/pkg/messages"
|
||||||
// EncodedProjectID *uint64 `json:"projectID"`
|
)
|
||||||
// TrackerVersion string `json:"trackerVersion"`
|
|
||||||
// RevID string `json:"revID"`
|
|
||||||
// UserUUID *string `json:"userUUID"`
|
|
||||||
// //UserOS string `json"userOS"` //hardcoded 'MacOS'
|
|
||||||
// UserOSVersion string `json:"userOSVersion"`
|
|
||||||
// UserDevice string `json:"userDevice"`
|
|
||||||
// Timestamp uint64 `json:"timestamp"`
|
|
||||||
// // UserDeviceType uint 0:phone 1:pad 2:tv 3:carPlay 5:mac
|
|
||||||
// // “performances”:{
|
|
||||||
// // “activeProcessorCount”:8,
|
|
||||||
// // “isLowPowerModeEnabled”:0,
|
|
||||||
// // “orientation”:0,
|
|
||||||
// // “systemUptime”:585430,
|
|
||||||
// // “batteryState”:0,
|
|
||||||
// // “thermalState”:0,
|
|
||||||
// // “batteryLevel”:0,
|
|
||||||
// // “processorCount”:8,
|
|
||||||
// // “physicalMemory”:17179869184
|
|
||||||
// // },
|
|
||||||
// }
|
|
||||||
// type response struct {
|
|
||||||
// Token string `json:"token"`
|
|
||||||
// ImagesHashList []string `json:"imagesHashList"`
|
|
||||||
// UserUUID string `json:"userUUID"`
|
|
||||||
// SESSION_ID uint64 `json:"SESSION_ID"` ///TEMP
|
|
||||||
// }
|
|
||||||
// startTime := time.Now()
|
|
||||||
// req := &request{}
|
|
||||||
// body := http.MaxBytesReader(w, r.Body, JSON_SIZE_LIMIT)
|
|
||||||
// //defer body.Close()
|
|
||||||
// if err := json.NewDecoder(body).Decode(req); err != nil {
|
|
||||||
// responseWithError(w, http.StatusBadRequest, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if req.EncodedProjectID == nil {
|
const FILES_SIZE_LIMIT int64 = 1e8 // 100Mb
|
||||||
// responseWithError(w, http.StatusForbidden, errors.New("ProjectID value required"))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// projectID := decodeProjectID(*(req.EncodedProjectID))
|
|
||||||
// if projectID == 0 {
|
|
||||||
// responseWithError(w, http.StatusUnprocessableEntity, errors.New("ProjectID value is invalid"))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// p, err := pgconn.GetProject(uint32(projectID))
|
|
||||||
// if err != nil {
|
|
||||||
// if postgres.IsNoRowsErr(err) {
|
|
||||||
// responseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active"))
|
|
||||||
// } else {
|
|
||||||
// responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// sessionID, err := flaker.Compose(req.Timestamp)
|
|
||||||
// if err != nil {
|
|
||||||
// responseWithError(w, http.StatusInternalServerError, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// userUUID := getUUID(req.UserUUID)
|
|
||||||
// country := geoIP.ExtractISOCodeFromHTTPRequest(r)
|
|
||||||
// expirationTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond)
|
|
||||||
|
|
||||||
// imagesHashList, err := s3.GetFrequentlyUsedKeys(*(req.EncodedProjectID)) // TODO: reuse index: ~ frequency * size
|
func startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) {
|
||||||
// if err != nil {
|
type request struct {
|
||||||
// responseWithError(w, http.StatusInternalServerError, err)
|
Token string `json:"token"`
|
||||||
// return
|
ProjectKey *string `json:"projectKey"`
|
||||||
// }
|
TrackerVersion string `json:"trackerVersion"`
|
||||||
|
RevID string `json:"revID"`
|
||||||
|
UserUUID *string `json:"userUUID"`
|
||||||
|
//UserOS string `json"userOS"` //hardcoded 'MacOS'
|
||||||
|
UserOSVersion string `json:"userOSVersion"`
|
||||||
|
UserDevice string `json:"userDevice"`
|
||||||
|
Timestamp uint64 `json:"timestamp"`
|
||||||
|
// UserDeviceType uint 0:phone 1:pad 2:tv 3:carPlay 5:mac
|
||||||
|
// “performances”:{
|
||||||
|
// “activeProcessorCount”:8,
|
||||||
|
// “isLowPowerModeEnabled”:0,
|
||||||
|
// “orientation”:0,
|
||||||
|
// “systemUptime”:585430,
|
||||||
|
// “batteryState”:0,
|
||||||
|
// “thermalState”:0,
|
||||||
|
// “batteryLevel”:0,
|
||||||
|
// “processorCount”:8,
|
||||||
|
// “physicalMemory”:17179869184
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
type response struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
ImagesHashList []string `json:"imagesHashList"`
|
||||||
|
UserUUID string `json:"userUUID"`
|
||||||
|
BeaconSizeLimit int64 `json:"beaconSizeLimit"`
|
||||||
|
SessionID string `json:"sessionID"`
|
||||||
|
}
|
||||||
|
startTime := time.Now()
|
||||||
|
req := &request{}
|
||||||
|
body := http.MaxBytesReader(w, r.Body, JSON_SIZE_LIMIT)
|
||||||
|
//defer body.Close()
|
||||||
|
if err := json.NewDecoder(body).Decode(req); err != nil {
|
||||||
|
responseWithError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// responseWithJSON(w, &response{
|
if req.ProjectKey == nil {
|
||||||
// Token: tokenizer.Compose(sessionID, uint64(expirationTime.UnixNano()/1e6)),
|
responseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required"))
|
||||||
// ImagesHashList: imagesHashList,
|
return
|
||||||
// UserUUID: userUUID,
|
}
|
||||||
// //TEMP:
|
|
||||||
// SESSION_ID: sessionID,
|
p, err := pgconn.GetProjectByKey(*req.ProjectKey)
|
||||||
// })
|
if err != nil {
|
||||||
// producer.Produce(topicRaw, sessionID, messages.Encode(&messages.IOSSessionStart{
|
if postgres.IsNoRowsErr(err) {
|
||||||
// Timestamp: req.Timestamp,
|
responseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active"))
|
||||||
// ProjectID: projectID,
|
} else {
|
||||||
// TrackerVersion: req.TrackerVersion,
|
responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
||||||
// RevID: req.RevID,
|
}
|
||||||
// UserUUID: userUUID,
|
return
|
||||||
// UserOS: "MacOS",
|
}
|
||||||
// UserOSVersion: req.UserOSVersion,
|
userUUID := getUUID(req.UserUUID)
|
||||||
// UserDevice: MapIOSDevice(req.UserDevice),
|
tokenData, err := tokenizer.Parse(req.Token)
|
||||||
// UserDeviceType: GetIOSDeviceType(req.UserDevice), // string `json:"userDeviceType"` // From UserDevice; ENUM ?
|
|
||||||
// UserCountry: country,
|
if err != nil { // Starting the new one
|
||||||
// }))
|
dice := byte(rand.Intn(100)) // [0, 100)
|
||||||
// }
|
if dice >= p.SampleRate {
|
||||||
|
responseWithError(w, http.StatusForbidden, errors.New("cancel"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ua := uaParser.ParseFromHTTPRequest(r)
|
||||||
|
if ua == nil {
|
||||||
|
responseWithError(w, http.StatusForbidden, errors.New("browser not recognized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionID, err := flaker.Compose(uint64(startTime.UnixNano() / 1e6))
|
||||||
|
if err != nil {
|
||||||
|
responseWithError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: if EXPIRED => send message for two sessions association
|
||||||
|
expTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond)
|
||||||
|
tokenData = &token.TokenData{sessionID, expTime.UnixNano() / 1e6}
|
||||||
|
|
||||||
|
country := geoIP.ExtractISOCodeFromHTTPRequest(r)
|
||||||
|
|
||||||
|
// The difference with web is mostly here:
|
||||||
|
producer.Produce(TOPIC_RAW, tokenData.ID, Encode(&IOSSessionStart{
|
||||||
|
Timestamp: req.Timestamp,
|
||||||
|
ProjectID: uint64(p.ProjectID),
|
||||||
|
TrackerVersion: req.TrackerVersion,
|
||||||
|
RevID: req.RevID,
|
||||||
|
UserUUID: userUUID,
|
||||||
|
UserOS: "IOS",
|
||||||
|
UserOSVersion: req.UserOSVersion,
|
||||||
|
UserDevice: MapIOSDevice(req.UserDevice),
|
||||||
|
UserDeviceType: GetIOSDeviceType(req.UserDevice),
|
||||||
|
UserCountry: country,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// imagesHashList, err := s3.GetFrequentlyUsedKeys(*(req.EncodedProjectID)) // TODO: reuse index: ~ frequency * size
|
||||||
|
// if err != nil {
|
||||||
|
// responseWithError(w, http.StatusInternalServerError, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
responseWithJSON(w, &response{
|
||||||
|
// ImagesHashList: imagesHashList,
|
||||||
|
Token: tokenizer.Compose(*tokenData),
|
||||||
|
UserUUID: userUUID,
|
||||||
|
SessionID: strconv.FormatUint(tokenData.ID, 10),
|
||||||
|
BeaconSizeLimit: BEACON_SIZE_LIMIT,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// func pushLateMessagesHandler(w http.ResponseWriter, r *http.Request) {
|
func pushLateMessagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// sessionData, err := tokenizer.ParseFromHTTPRequest(r)
|
sessionData, err := tokenizer.ParseFromHTTPRequest(r)
|
||||||
// if err != nil && err != token.EXPIRED {
|
if err != nil && err != token.EXPIRED {
|
||||||
// responseWithError(w, http.StatusUnauthorized, err)
|
responseWithError(w, http.StatusUnauthorized, err)
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// // Check timestamps here?
|
// Check timestamps here?
|
||||||
// pushMessages(w, r, sessionData.ID)
|
pushMessages(w, r, sessionData.ID)
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
// func iosImagesUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func iosImagesUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// r.Body = http.MaxBytesReader(w, r.Body, FILES_SIZE_LIMIT)
|
sessionData, err := tokenizer.ParseFromHTTPRequest(r)
|
||||||
// // defer r.Body.Close()
|
if err != nil { // Should accept expired token?
|
||||||
// err := r.ParseMultipartForm(1e5) // 100Kb
|
responseWithError(w, http.StatusUnauthorized, err)
|
||||||
// if err == http.ErrNotMultipart || err == http.ErrMissingBoundary {
|
return
|
||||||
// responseWithError(w, http.StatusUnsupportedMediaType, err)
|
}
|
||||||
// // } else if err == multipart.ErrMessageTooLarge // if non-files part exceeds 10 MB
|
|
||||||
// } else if err != nil {
|
|
||||||
// responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(r.MultipartForm.Value["projectID"]) == 0 {
|
r.Body = http.MaxBytesReader(w, r.Body, FILES_SIZE_LIMIT)
|
||||||
// responseWithError(w, http.StatusBadRequest, errors.New("projectID parameter required")) // status for missing/wrong parameter?
|
// defer r.Body.Close()
|
||||||
// return
|
err = r.ParseMultipartForm(1e6) // ~1Mb
|
||||||
// }
|
if err == http.ErrNotMultipart || err == http.ErrMissingBoundary {
|
||||||
// // encodedProjectID, err := strconv.ParseUint(r.MultipartForm.Value["projectID"][0], 10, 64)
|
responseWithError(w, http.StatusUnsupportedMediaType, err)
|
||||||
// // projectID := decodeProjectID(encodedProjectID)
|
// } else if err == multipart.ErrMessageTooLarge // if non-files part exceeds 10 MB
|
||||||
// // if projectID == 0 || err != nil {
|
} else if err != nil {
|
||||||
// // responseWithError(w, http.StatusUnprocessableEntity, errors.New("projectID value is incorrect"))
|
responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging
|
||||||
// // return
|
}
|
||||||
// // }
|
|
||||||
// prefix := r.MultipartForm.Value["projectID"][0] + "/" //strconv.FormatUint(uint64(projectID), 10) + "/"
|
|
||||||
|
|
||||||
// for _, fileHeaderList := range r.MultipartForm.File {
|
if (r.MultipartForm == nil) {
|
||||||
// for _, fileHeader := range fileHeaderList {
|
responseWithError(w, http.StatusInternalServerError, errors.New("Multipart not parsed"))
|
||||||
// file, err := fileHeader.Open()
|
}
|
||||||
// if err != nil {
|
|
||||||
// continue // TODO: send server error or accumulate successful files
|
|
||||||
// }
|
|
||||||
// key := prefix + fileHeader.Filename // TODO: Malicious image put: use jwt?
|
|
||||||
// go s3.Upload(file, key, "image/png", false)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// w.WriteHeader(http.StatusOK)
|
if len(r.MultipartForm.Value["projectKey"]) == 0 {
|
||||||
// }
|
responseWithError(w, http.StatusBadRequest, errors.New("projectKey parameter missing")) // status for missing/wrong parameter?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := r.MultipartForm.Value["projectKey"][0] + "/" + strconv.FormatUint(sessionData.ID, 10) + "/"
|
||||||
|
|
||||||
|
for _, fileHeaderList := range r.MultipartForm.File {
|
||||||
|
for _, fileHeader := range fileHeaderList {
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
continue // TODO: send server error or accumulate successful files
|
||||||
|
}
|
||||||
|
key := prefix + fileHeader.Filename
|
||||||
|
go s3.Upload(file, key, "image/png", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("Request: %v - %v ", r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/":
|
case "/":
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
@ -100,34 +103,34 @@ func main() {
|
||||||
default:
|
default:
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
// case "/v1/ios/start":
|
case "/v1/ios/start":
|
||||||
// switch r.Method {
|
switch r.Method {
|
||||||
// case http.MethodPost:
|
case http.MethodPost:
|
||||||
// startSessionHandlerIOS(w, r)
|
startSessionHandlerIOS(w, r)
|
||||||
// default:
|
default:
|
||||||
// w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
// }
|
}
|
||||||
// case "/v1/ios/append":
|
case "/v1/ios/i":
|
||||||
// switch r.Method {
|
switch r.Method {
|
||||||
// case http.MethodPost:
|
case http.MethodPost:
|
||||||
// pushMessagesHandler(w, r)
|
pushMessagesHandler(w, r)
|
||||||
// default:
|
default:
|
||||||
// w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
// }
|
}
|
||||||
// case "/v1/ios/late":
|
case "/v1/ios/late":
|
||||||
// switch r.Method {
|
switch r.Method {
|
||||||
// case http.MethodPost:
|
case http.MethodPost:
|
||||||
// pushLateMessagesHandler(w, r)
|
pushLateMessagesHandler(w, r)
|
||||||
// default:
|
default:
|
||||||
// w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
// }
|
}
|
||||||
// case "/v1/ios/images":
|
case "/v1/ios/images":
|
||||||
// switch r.Method {
|
switch r.Method {
|
||||||
// case http.MethodPost:
|
case http.MethodPost:
|
||||||
// iosImagesUploadHandler(w, r)
|
iosImagesUploadHandler(w, r)
|
||||||
// default:
|
default:
|
||||||
// w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
// }
|
}
|
||||||
default:
|
default:
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ func responseWithJSON(w http.ResponseWriter, res interface{}) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(body)
|
w.Write(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import stl from './storage.css';
|
||||||
function getActionsName(type) {
|
function getActionsName(type) {
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case STORAGE_TYPES.MOBX:
|
case STORAGE_TYPES.MOBX:
|
||||||
return "EVENTS";
|
return "MUTATIONS";
|
||||||
case STORAGE_TYPES.VUEX:
|
case STORAGE_TYPES.VUEX:
|
||||||
return "MUTATIONS";
|
return "MUTATIONS";
|
||||||
default:
|
default:
|
||||||
|
|
@ -141,7 +141,7 @@ export default class Storage extends React.PureComponent {
|
||||||
break;
|
break;
|
||||||
case STORAGE_TYPES.MOBX:
|
case STORAGE_TYPES.MOBX:
|
||||||
src = item.payload;
|
src = item.payload;
|
||||||
name = `@${item.type} ${src && src.name}`;
|
name = `@${item.type} ${src && src.type}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ spec:
|
||||||
{{- if eq .Values.pvc.name "hostPath" }}
|
{{- if eq .Values.pvc.name "hostPath" }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: {{ .Values.pvc.mountPath }}
|
- mountPath: {{ .Values.pvc.mountPath }}
|
||||||
name: {{ .Values.pvc.name }}
|
name: datadir
|
||||||
volumes:
|
volumes:
|
||||||
- name: mydir
|
- name: datadir
|
||||||
hostPath:
|
hostPath:
|
||||||
# Ensure the file directory is created.
|
# Ensure the file directory is created.
|
||||||
path: {{ .Values.pvc.hostMountPath }}
|
path: {{ .Values.pvc.hostMountPath }}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,9 @@ metadata:
|
||||||
namespace: {{ .Release.Namespace }}
|
namespace: {{ .Release.Namespace }}
|
||||||
data:
|
data:
|
||||||
location.list: |-
|
location.list: |-
|
||||||
|
location ~* /general_stats {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
location /healthz {
|
location /healthz {
|
||||||
return 200 'OK';
|
return 200 'OK';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
file="{{ item|basename }}"
|
file="{{ item|basename }}"
|
||||||
kubectl exec -n db postgresql-postgresql-0 -- /bin/bash -c "rm -rf /tmp/$file"
|
kubectl exec -n db postgresql-postgresql-0 -- /bin/bash -c "rm -rf /tmp/$file"
|
||||||
kubectl cp -n db $file postgresql-postgresql-0:/tmp/
|
kubectl cp -n db $file postgresql-postgresql-0:/tmp/
|
||||||
kubectl exec -n db postgresql-postgresql-0 -- /bin/bash -c "PGPASSWORD=asayerPostgres psql -U postgres -f /tmp/$file" &> "{{ playbook_dir }}"/postgresql_init.log
|
kubectl exec -n db postgresql-postgresql-0 -- /bin/bash -c "PGPASSWORD=asayerPostgres psql -v ON_ERROR_STOP=1 -U postgres -f /tmp/$file" &> "{{ playbook_dir }}"/postgresql_init.log
|
||||||
args:
|
args:
|
||||||
chdir: db/init_dbs/postgresql
|
chdir: db/init_dbs/postgresql
|
||||||
with_fileglob:
|
with_fileglob:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ image:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
env:
|
env:
|
||||||
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
||||||
POSTGRES_STRING: "postgres://{{postgres_db_user}}:{{postgres_db_password}}@{{postgres_endpoint}}:{{postgres_port}}"
|
POSTGRES_STRING: "postgres://{{postgres_db_user}}:{{postgres_db_password}}@{{postgres_endpoint}}:{{postgres_port}}/{{ postgres_db_name }}"
|
||||||
|
|
||||||
{% if not (docker_registry_username is defined and docker_registry_username and docker_registry_password is defined and docker_registry_password) %}
|
{% if not (docker_registry_username is defined and docker_registry_username and docker_registry_password is defined and docker_registry_password) %}
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ image:
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: "{{ minio_access_key }}"
|
AWS_ACCESS_KEY_ID: "{{ minio_access_key }}"
|
||||||
AWS_SECRET_ACCESS_KEY: "{{ minio_secret_key }}"
|
AWS_SECRET_ACCESS_KEY: "{{ minio_secret_key }}"
|
||||||
|
S3_BUCKET_ASSETS: "{{ assets_bucket }}"
|
||||||
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
||||||
AWS_ENDPOINT: "{{ s3_endpoint }}"
|
AWS_ENDPOINT: "{{ s3_endpoint }}"
|
||||||
AWS_REGION: "{{ aws_region }}"
|
AWS_REGION: "{{ aws_region }}"
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ env:
|
||||||
S3_SECRET: "{{ minio_secret_key }}"
|
S3_SECRET: "{{ minio_secret_key }}"
|
||||||
sourcemaps_bucket_key: "{{ minio_access_key }}"
|
sourcemaps_bucket_key: "{{ minio_access_key }}"
|
||||||
sourcemaps_bucket_secret: "{{ minio_secret_key }}"
|
sourcemaps_bucket_secret: "{{ minio_secret_key }}"
|
||||||
S3_HOST: "https://{{ domain_name }}"
|
|
||||||
SITE_URL: "https://{{ domain_name }}"
|
SITE_URL: "https://{{ domain_name }}"
|
||||||
jwt_secret: "{{ jwt_secret_key }}"
|
jwt_secret: "{{ jwt_secret_key }}"
|
||||||
pg_host: "{{ postgres_endpoint }}"
|
pg_host: "{{ postgres_endpoint }}"
|
||||||
pg_port: "{{ postgres_port }}"
|
pg_port: "{{ postgres_port }}"
|
||||||
pg_dbname: "{{ postgres_port }}"
|
pg_dbname: "{{ postgres_db_name }}"
|
||||||
pg_user: "{{ postgres_db_user }}"
|
pg_user: "{{ postgres_db_user }}"
|
||||||
pg_password: "{{ postgres_db_password }}"
|
pg_password: "{{ postgres_db_password }}"
|
||||||
EMAIL_HOST: "{{ email_host }}"
|
EMAIL_HOST: "{{ email_host }}"
|
||||||
|
|
@ -29,8 +28,18 @@ env:
|
||||||
EMAIL_SSL_KEY: "{{ email_ssl_key }}"
|
EMAIL_SSL_KEY: "{{ email_ssl_key }}"
|
||||||
EMAIL_SSL_CERT: "{{ email_ssl_cert }}"
|
EMAIL_SSL_CERT: "{{ email_ssl_cert }}"
|
||||||
EMAIL_FROM: "{{ email_from }}"
|
EMAIL_FROM: "{{ email_from }}"
|
||||||
AWS_DEFAULT_REGION: "{{ aws_default_region }}"
|
AWS_DEFAULT_REGION: "{{ aws_region }}"
|
||||||
sessions_region: "{{ aws_default_region }}"
|
sessions_region: "{{ aws_region }}"
|
||||||
|
sessions_bucket: "{{ recordings_bucket }}"
|
||||||
|
sourcemaps_bucket: "{{ sourcemaps_bucket }}"
|
||||||
|
js_cache_bucket: "{{ assets_bucket }}"
|
||||||
|
# In case of minio, the instance is running inside kuberntes,
|
||||||
|
# which is accessible via nginx ingress.
|
||||||
|
{% if s3_endpoint == "http://minio.db.svc.cluster.local:9000" %}
|
||||||
|
S3_HOST: "https://{{ domain_name }}"
|
||||||
|
{% else %}
|
||||||
|
S3_HOST: "{{ s3_endpoint }}"
|
||||||
|
{% endif %}
|
||||||
{% if env is defined and env.chalice is defined and env.chalice%}
|
{% if env is defined and env.chalice is defined and env.chalice%}
|
||||||
{{ env.chalice | to_nice_yaml | trim | indent(2) }}
|
{{ env.chalice | to_nice_yaml | trim | indent(2) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ image:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
env:
|
env:
|
||||||
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
||||||
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_password }}@{{ postgres_endpoint }}:{{ postgres_port }}"
|
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_db_password }}@{{ postgres_endpoint }}:{{ postgres_port }}/{{ postgres_db_name }}"
|
||||||
REDIS_STRING: "{{ redis_endpoint }}"
|
REDIS_STRING: "{{ redis_endpoint }}"
|
||||||
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
||||||
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ env:
|
||||||
AWS_SECRET_ACCESS_KEY: "{{ minio_secret_key }}"
|
AWS_SECRET_ACCESS_KEY: "{{ minio_secret_key }}"
|
||||||
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
||||||
AWS_REGION: "{{ aws_region }}"
|
AWS_REGION: "{{ aws_region }}"
|
||||||
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_password }}@{{ postgres_endpoint }}:{{ postgres_port }}"
|
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_db_password }}@{{ postgres_endpoint }}:{{ postgres_port }}/{{ postgres_db_name }}"
|
||||||
REDIS_STRING: "{{ redis_endpoint }}"
|
REDIS_STRING: "{{ redis_endpoint }}"
|
||||||
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
||||||
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ image:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
env:
|
env:
|
||||||
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
LICENSE_KEY: "{{ enterprise_edition_license }}"
|
||||||
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_password }}@{{ postgres_endpoint }}:{{ postgres_port }}"
|
POSTGRES_STRING: "postgres://{{ postgres_db_user }}:{{ postgres_db_password }}@{{ postgres_endpoint }}:{{ postgres_port }}/{{ postgres_db_name }}"
|
||||||
#
|
#
|
||||||
REDIS_STRING: "{{ redis_endpoint }}"
|
REDIS_STRING: "{{ redis_endpoint }}"
|
||||||
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ env:
|
||||||
AWS_ENDPOINT: "{{ s3_endpoint }}"
|
AWS_ENDPOINT: "{{ s3_endpoint }}"
|
||||||
AWS_REGION_WEB: "{{ aws_region }}"
|
AWS_REGION_WEB: "{{ aws_region }}"
|
||||||
AWS_REGION_IOS: "{{ aws_region }}"
|
AWS_REGION_IOS: "{{ aws_region }}"
|
||||||
|
S3_BUCKET_WEB: "{{ recordings_bucket }}"
|
||||||
|
S3_BUCKET_IOS: "{{ recordings_bucket }}"
|
||||||
REDIS_STRING: "{{ redis_endpoint }}"
|
REDIS_STRING: "{{ redis_endpoint }}"
|
||||||
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
KAFKA_SERVERS: "{{ kafka_endpoint }}"
|
||||||
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
KAFKA_USE_SSL: "{{ kafka_ssl }}"
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ db_resource_override:
|
||||||
## Sane defaults
|
## Sane defaults
|
||||||
s3_endpoint: "http://minio.db.svc.cluster.local:9000"
|
s3_endpoint: "http://minio.db.svc.cluster.local:9000"
|
||||||
aws_region: "us-east-1"
|
aws_region: "us-east-1"
|
||||||
|
assets_bucket: sessions-assets
|
||||||
|
recordings_bucket: mobs
|
||||||
|
sourcemaps_bucket: sourcemaps
|
||||||
kafka_endpoint: kafka.db.svc.cluster.local:9042
|
kafka_endpoint: kafka.db.svc.cluster.local:9042
|
||||||
kafka_ssl: false
|
kafka_ssl: false
|
||||||
postgres_endpoint: postgresql.db.svc.cluster.local
|
postgres_endpoint: postgresql.db.svc.cluster.local
|
||||||
|
|
|
||||||
7
tracker/tracker/package-lock.json
generated
7
tracker/tracker/package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@openreplay/tracker",
|
"name": "@openreplay/tracker",
|
||||||
"version": "3.4.1",
|
"version": "3.4.4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -293,11 +293,6 @@
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@medv/finder": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@medv/finder/-/finder-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-gV4jOsGpiWNDGd8Dw7tod1Fc9Gc7StaOT4oZ/6srHRWtsHU+HYWzmkYsa3Qy/z0e9tY1WpJ9wWdBFGskfbzoug=="
|
|
||||||
},
|
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@openreplay/tracker",
|
"name": "@openreplay/tracker",
|
||||||
"description": "The OpenReplay tracker main package",
|
"description": "The OpenReplay tracker main package",
|
||||||
"version": "3.4.4",
|
"version": "3.4.5",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"logging",
|
"logging",
|
||||||
"replay"
|
"replay"
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
"typescript": "^4.3.4"
|
"typescript": "^4.3.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medv/finder": "^2.0.0",
|
|
||||||
"error-stack-parser": "^2.0.6"
|
"error-stack-parser": "^2.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export default class App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(this.options.__debug_log) {
|
if(this.options.__debug_log) {
|
||||||
warn("OpenReplay errror: ", context, e)
|
warn("OpenReplay error: ", context, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,9 +153,13 @@ export default class App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommitCallback(cb: CommitCallback): void {
|
attachCommitCallback(cb: CommitCallback): void {
|
||||||
this.commitCallbacks.push(cb)
|
this.commitCallbacks.push(cb)
|
||||||
}
|
}
|
||||||
|
// @Depricated (TODO: remove in 3.5.*)
|
||||||
|
addCommitCallback(cb: CommitCallback): void {
|
||||||
|
this.attachCommitCallback(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
safe<T extends (...args: any[]) => void>(fn: T): T {
|
safe<T extends (...args: any[]) => void>(fn: T): T {
|
||||||
|
|
|
||||||
|
|
@ -411,11 +411,13 @@ export default class Observer {
|
||||||
|
|
||||||
private iframeObservers: Observer[] = [];
|
private iframeObservers: Observer[] = [];
|
||||||
private handleIframe(iframe: HTMLIFrameElement): void {
|
private handleIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
let context: Window | null = null
|
||||||
const handle = () => {
|
const handle = () => {
|
||||||
const context = iframe.contentWindow as Window | null
|
|
||||||
const id = this.app.nodes.getID(iframe)
|
const id = this.app.nodes.getID(iframe)
|
||||||
if (!context || id === undefined) { return }
|
if (id === undefined) { return }
|
||||||
|
if (iframe.contentWindow === context) { return }
|
||||||
|
context = iframe.contentWindow as Window | null;
|
||||||
|
if (!context) { return }
|
||||||
const observer = new Observer(this.app, this.options, context)
|
const observer = new Observer(this.app, this.options, context)
|
||||||
this.iframeObservers.push(observer)
|
this.iframeObservers.push(observer)
|
||||||
observer.observeIframe(id, context)
|
observer.observeIframe(id, context)
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ import { Options as AppOptions } from './app';
|
||||||
import { Options as ConsoleOptions } from './modules/console';
|
import { Options as ConsoleOptions } from './modules/console';
|
||||||
import { Options as ExceptionOptions } from './modules/exception';
|
import { Options as ExceptionOptions } from './modules/exception';
|
||||||
import { Options as InputOptions } from './modules/input';
|
import { Options as InputOptions } from './modules/input';
|
||||||
import { Options as MouseOptions } from './modules/mouse';
|
|
||||||
import { Options as PerformanceOptions } from './modules/performance';
|
import { Options as PerformanceOptions } from './modules/performance';
|
||||||
import { Options as TimingOptions } from './modules/timing';
|
import { Options as TimingOptions } from './modules/timing';
|
||||||
|
|
||||||
export type Options = Partial<
|
export type Options = Partial<
|
||||||
AppOptions & ConsoleOptions & ExceptionOptions & InputOptions & MouseOptions & PerformanceOptions & TimingOptions
|
AppOptions & ConsoleOptions & ExceptionOptions & InputOptions & PerformanceOptions & TimingOptions
|
||||||
> & {
|
> & {
|
||||||
projectID?: number; // For the back compatibility only (deprecated)
|
projectID?: number; // For the back compatibility only (deprecated)
|
||||||
projectKey: string;
|
projectKey: string;
|
||||||
|
|
@ -98,7 +98,7 @@ export default class API {
|
||||||
Exception(this.app, options);
|
Exception(this.app, options);
|
||||||
Img(this.app);
|
Img(this.app);
|
||||||
Input(this.app, options);
|
Input(this.app, options);
|
||||||
Mouse(this.app, options);
|
Mouse(this.app);
|
||||||
Timing(this.app, options);
|
Timing(this.app, options);
|
||||||
Performance(this.app, options);
|
Performance(this.app, options);
|
||||||
Scroll(this.app);
|
Scroll(this.app);
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendConsoleLog = app.safe((level: string, args: any[]): void =>
|
const sendConsoleLog = app.safe((level: string, args: unknown[]): void =>
|
||||||
app.send(new ConsoleLog(level, printf(args))),
|
app.send(new ConsoleLog(level, printf(args))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -121,18 +121,36 @@ export default function (app: App, opts: Partial<Options>): void {
|
||||||
app.attachStartCallback(reset);
|
app.attachStartCallback(reset);
|
||||||
app.ticker.attach(reset, 33, false);
|
app.ticker.attach(reset, 33, false);
|
||||||
|
|
||||||
options.consoleMethods.forEach((method) => {
|
const patchConsole = (console: Console) =>
|
||||||
if (consoleMethods.indexOf(method) === -1) {
|
options.consoleMethods!.forEach((method) => {
|
||||||
console.error(`OpenReplay: unsupported console method "${method}"`);
|
if (consoleMethods.indexOf(method) === -1) {
|
||||||
return;
|
console.error(`OpenReplay: unsupported console method "${method}"`);
|
||||||
}
|
|
||||||
const fn = (console as any)[method];
|
|
||||||
(console as any)[method] = function (...args: any[]): void {
|
|
||||||
fn.apply(this, args);
|
|
||||||
if (n++ > options.consoleThrottling) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendConsoleLog(method, args);
|
const fn = (console as any)[method];
|
||||||
};
|
(console as any)[method] = function (...args: unknown[]): void {
|
||||||
});
|
fn.apply(this, args);
|
||||||
|
if (n++ > options.consoleThrottling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendConsoleLog(method, args);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
patchConsole(window.console);
|
||||||
|
|
||||||
|
app.nodes.attachNodeCallback(node => {
|
||||||
|
if (node instanceof HTMLIFrameElement) {
|
||||||
|
let context = node.contentWindow
|
||||||
|
if (context) {
|
||||||
|
patchConsole((context as (Window & typeof globalThis)).console)
|
||||||
|
}
|
||||||
|
app.attachEventListener(node, "load", () => {
|
||||||
|
if (node.contentWindow !== context) {
|
||||||
|
context = node.contentWindow
|
||||||
|
patchConsole((context as (Window & typeof globalThis)).console)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,5 +47,5 @@ export default function (app: App): void {
|
||||||
const observer: PerformanceObserver = new PerformanceObserver((list) =>
|
const observer: PerformanceObserver = new PerformanceObserver((list) =>
|
||||||
list.getEntries().forEach(longTask),
|
list.getEntries().forEach(longTask),
|
||||||
);
|
);
|
||||||
observer.observe({ entryTypes: ['longtask'], buffered: true });
|
observer.observe({ entryTypes: ['longtask'] });
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,30 @@
|
||||||
import type { Options as FinderOptions } from '../vendors/finder/finder';
|
|
||||||
import { finder } from '../vendors/finder/finder';
|
|
||||||
import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils';
|
import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils';
|
||||||
import App from '../app';
|
import App from '../app';
|
||||||
import { MouseMove, MouseClick } from '../../messages';
|
import { MouseMove, MouseClick } from '../../messages';
|
||||||
import { getInputLabel } from './input';
|
import { getInputLabel } from './input';
|
||||||
|
|
||||||
|
function _getSelector(target: Element): string {
|
||||||
|
let el: Element | null = target
|
||||||
|
let selector: string | null = null
|
||||||
|
do {
|
||||||
|
if (el.id) {
|
||||||
|
return `#${el.id}` + (selector ? ` > ${selector}` : '')
|
||||||
|
}
|
||||||
|
selector =
|
||||||
|
el.className.split(' ')
|
||||||
|
.map(cn => cn.trim())
|
||||||
|
.filter(cn => cn !== '')
|
||||||
|
.reduce((sel, cn) => `${sel}.${cn}`, el.tagName.toLowerCase()) +
|
||||||
|
(selector ? ` > ${selector}` : '');
|
||||||
|
if (el === document.body) {
|
||||||
|
return selector
|
||||||
|
}
|
||||||
|
el = el.parentElement
|
||||||
|
} while (el !== document.body && el !== null)
|
||||||
|
return selector
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: fix (typescript doesn't allow work when the guard is inside the function)
|
||||||
function getTarget(target: EventTarget | null): Element | null {
|
function getTarget(target: EventTarget | null): Element | null {
|
||||||
if (target instanceof Element) {
|
if (target instanceof Element) {
|
||||||
return _getTarget(target);
|
return _getTarget(target);
|
||||||
|
|
@ -72,26 +92,11 @@ function getTargetLabel(target: Element): string {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeatmapsOptions {
|
export default function (app: App): void {
|
||||||
finder: FinderOptions,
|
// const options: Options = Object.assign(
|
||||||
}
|
// {},
|
||||||
|
// opts,
|
||||||
export interface Options {
|
// );
|
||||||
heatmaps: boolean | HeatmapsOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (app: App, opts: Partial<Options>): void {
|
|
||||||
const options: Options = Object.assign(
|
|
||||||
{
|
|
||||||
heatmaps: false // {
|
|
||||||
// finder: {
|
|
||||||
// threshold: 5,
|
|
||||||
// maxNumberOfTries: 600,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mousePositionX = -1;
|
let mousePositionX = -1;
|
||||||
let mousePositionY = -1;
|
let mousePositionY = -1;
|
||||||
|
|
@ -115,9 +120,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
||||||
|
|
||||||
const selectorMap: {[id:number]: string} = {};
|
const selectorMap: {[id:number]: string} = {};
|
||||||
function getSelector(id: number, target: Element): string {
|
function getSelector(id: number, target: Element): string {
|
||||||
if (options.heatmaps === false) { return '' }
|
return selectorMap[id] = selectorMap[id] || _getSelector(target);
|
||||||
return selectorMap[id] = selectorMap[id] ||
|
|
||||||
finder(target, options.heatmaps === true ? undefined : options.heatmaps.finder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.attachEventListener(
|
app.attachEventListener(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue