* feat(backend): implemented unzipping for http requests with gzip content-type * fix(tracker): rm unused import * change(tracker): configure automatic headers, compress anything bigger than 24k, add third party lib to list * feat(backend): using custom library for unzipping request body * feat(backend): added extra logs * feat(backend): more debug logs * feat(backend): added compression threshold to start request * change(tracker): support compressionThreshold in tracker * feat(backend): debug log for body content * feat(backend): removed debug logs in http methods * change(tracker): fix priority sending, remove dead code, * feat(backend): removed debug logs in http methods * Enable session encryption (#1121) * feat(backend): enable session encryption * feat(backend): fixed updated method name in failover algo * feat(backend): disable encryption by default * change(tracker): fix iframe network handling * change(ui): add toast for recording error * Encryption metrics (#1151) * feat(backend): added metric to measure the duration of session encryption * feat(backend): enabled ecnryption * feat(backend): fixed typo issue in packSession method * change(ui): change error toast for rec * change(ui): add tooltip for added live sessions * chore(helm): disabling redis string if not enabled (#1153) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * change(player): fix typos; priority for 1st dom file * fix(player): priority and await for message processing * change(ui) - player improvements (#1164) * change(ui) - player - back button spacing * change(ui) - onboarding - changes * change(ui) - onboarding - changes * change(ui) - integrations gap-4 * change(ui) - install script copy button styles * change(ui) - copy button in account settings * fix(ui) - error details modal loader position * change(ui) - share popup styles * change(ui) - player improvements * change(ui) - player improvements - playback speed with menu * change(ui) - player improvements - current timezone * change(ui) - player improvements - autoplay options * fix(ui) - user sessions modal - navigation * feat(player): lazy JS DOM node creation; (need fixes for reaching full potential) * fix(player): drasticly reduce amount of node getter call during virtual node insertion * feat(player/VirtualDOM): OnloadVRoot & OnloadStyleSheet for lazy iframe innerContent initialisation & elimination of forceInsertion requirement in this case;; few renamings * style(player): few renamings; comments improved * feat(player/DOMManager): VirtualNodes insertion prioretization (for styles) * fix(player): cursor svg with light border for better visibility on dark backgrounds * change(ui) - session bookmarks remove from the list and copy options * chore(helm): Updating frontend image release (#1166) * chore(helm): Updating frontend image release * fix(helm): PG custom port Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(player): consider stringDict before any CreateDocument (fastfix) * style(player/DOMManager/safeCSSRules): depend on interfaces * fixup! fix(player): consider stringDict before any CreateDocument (fastfix) * fix(player): proper unmount * fix(helm): Variable override, prioriry to the user created one. (#1173) * fix(ui) - search url to wait for metadata to load * fix(tracker): optimise node counting * fix(tracker): changelog * fix(ui) - sessions reload (#1177) * fix(tracker): fix iframe network requests tracking * fix(ui) - check for error status and force logout (#1179) * fix(ui) - token expire * fix(ui) - token expire * change(player): manual decompression for encrypted files * change(player): detect gzip file after decoding * change(ui) - show projects in menu for all * [Storage] different order to compress and encrypt (#1182) * feat(backend): try to compress and encrypt in a new way * chore(helm): Update cors headers for http Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(ui): fix assist tooltip * change(ui): add sleep icon for inactive assist users * fix(ui): fix player automatic jump and start issues * Update .env.sample * Update cli for fetch latest patches and kubeconfig file hierarchy (#1183) * chore(helm): Kubeconfig file hierarchy Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): openreplay -u fetches update from current version, unless flag set Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): Updating comment (#1184) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): Adding option to keep backup directories (#1185) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): removing log message (#1186) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): Updating comment (#1188) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(helm): Annotation inject order Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(player): fix vroot context getter * feat(ui): display real session time * change(ui) - clearsearch styling on disable * change(ui) - session url changes * refactor(player/DOMManager): notMountedChildren rename * change(ui) - check if saved search present * change(ui) - player control icons and other changes * change(ui) - password validations * change(ui) - password validations * chore(helm): Override image pull policy (#1199) * change(ui) - player user steps improvements (#1201) * change(ui) - user steps * change(ui) - user steps * change(ui) - user steps * change(ui) - user steps - icon and other styles * fix(ui) - xray verticle line sync on resize * change(ui) - projects remove the status check * fix(cli): Proper git tag propegation (#1202) and logging of clone Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * Adding maintenance page * Improved session compression (#1200) * feat(backend): implemented new compression * chore(crons): Updating dockerfile Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * change(ui) - insights improvements * fix(ui) - url search params remove [] for keys * fix(player): fix dict reset * Remove message index from mob file (#1213) * feat(backend): removed message index from mob file messages * feat(backend): remove duplicated messages (by message index) * feat(backend): added MAX_INDEX at the begining of session to indicate a new version of mob file * feat(backend): added comments to code * change(ui): remove indexes from msgs * change(player): remove 8 byte skip for index * change(player): remove indexes * change(player): bugifx * change(tracker): update tests * change(tracker): remove batch writer changes * change(player): fix comments * feat(backend): updated go.mod file * change(player): change time str * feat(player): added mice trail * change(player): change trail color * change(player): change styles for buttons * chore(build): Don't commit chart change for ee patch (#1216) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * change(ui) updated recaptcha lib - which causing an issue with state reading * change(ui) - no content icon updates from metadata and webhooks * change(player): make cursor icon bigger * fix(player): fix virtualization * fix(player): fix virtualization * fix(ui) - onboarding project edit * change(ui) - no content graphic for projects, and svg component changes * change(ui) - events filter placeholder * change(ui) - ui feedback on user steps * change(ui): add more detials to health status * [Storage] timestamp sorting and filtering (#1218) * feat(backend): combined sorting by index and timestamp * feat(backend): write the only last timestamp message in a row * change(ui) - textarea styles * change(ui) - button text color * change(ui): add more detials to health status * fix(ui): fix screen rec error handling * fix(ui): fix screen rec stopping * fix(tracker): fix q sender token mismatch during assist connection * change(ui) - assist recordings pagination api * change(ui) - assist recordings pagination api * fix(ui) - not popup conflict with timeline tooltip * Updating version * change(tracker): 7.0.0. set max amount on restarts for compression error * fix(ui) - active menu link * fix redis endpoint and chalice health endpoints (#1138) * chore(helm): Adding redis string from global config Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(chalice): health check url for alerts and assist Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(ee): chalice health check (#1142) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): Adding verbose logging (#1144) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(helm): Adding option for records bucket (#1146) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(build): Bump image version of frontend assets while building (#1149) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * feat(chalice): fixed jobs execution * feat(chalice): configurable mobs expiration * feat(chalice): changes * feat(chalice): refactored Jobs feat(chalice): added limits on Jobs * chore(build): test patch branch * chore(build): testing EE cron-Jobs * Add files via upload (#1156) * Add files via upload (#1157) * chore(helm): Enabling redis string for helm template variable (#1159) fix #1158 Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * Changing default encryption to false (#1161) * Updated hero * feat(chalice): return all records if date is not specified * feat(chalice): refactored records list * Moving cli to scripts folder (#1196) * Revert "Moving cli to scripts folder (#1196)" (#1197) This reverts commitc947e48d99. * feat(chalice): support old FilterType * fix(ui) - alert form crash * fix(ui) - alert form crash * fix(ui) - assist menu status * Redshift connector (#1170) * Updated dependancies for redshift connector, changed os module for python-decouple module * Updated service and images * Updated message protocol, added exception for BatchMetadata when version is 0 (we apply old read method) * fixed load error from s3 to redshift. null values for string columns are now empty strings ("") * Added file test consumer_async.py: reads every 3 minutes kafka raw and send task in background to upload to cloud * Added method to skip messages that are not inserted to cloud * Added logs into consumer_async. Changed urls and issues in sessions table from list to string * Split between messages for sessions table and for events table * Updated redshift tables * Fixed small issue in query redshift_sessions.sql * Updated Dockerfiles. Cleaned logs of consumer_async. Updated/Fixed tables. Transformed Nan as NULL for VARCHAR columns * Added error handler for sql dropped connection * chore(docker): Optimize docker builds Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * Variables renamed * Adding compression libraries * Set default value of count events to 0 (instead of NULL) when event did not occur * Added support specific project tracking. Added PG handler to connect to sessions table * Added method to update values in db connection for sessions ended and restarted * Removing intelligent file copying * chore(connector): Build file Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * Adding connection pool for pg * Renaming and optimizing * Fixed issue of missing information of sessions --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> Co-authored-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(build): Parallel build Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * feat(chalice): changed release version feat(assist): changed release version feat(peers): changed release version feat(sourcemaps-reader): changed release version feat(chalice): enhanced health-check feat(DB): sessions_count table to keep status * feat(chalice): changed release version * feat(chalice): refactored projects code * feat(chalice): refactored projects code feat(chalice): sessions-check-flag every hour feat(chalice): sessions-check-delta set to 4 hours * feat(chalice): use experimental session search for metrics * feat(chalice): projects stats for health-check feat(DB): projects stats for health-check feat(crons): projects stats for health-check * feat(chalice): changed projects stats for health-check feat(crons): cahnged projects stats for health-check chore(helm): projectStats cron every 18 min chore(helm): projectStats-fix cron every Sunday at 5am * feat(crons): reorganized crons * feat(chalice): fixed typo * feat(chalice): changed health-check response * feat(crons): changed health-check response * (feat): Chalice - Allow SAML users to login with non-password methods as well as the usual password method, for example Windows Integrated Authentication * Move security field to correct area under SAML2 settings * feat(chalice): format code * feat(chalice): changed recordings response * feat(crons): fixed health check cron feat(crons): refactored main * feat(chalice): changed recordings response feat(chalice): updated dependencies feat(crons): updated dependencies feat(alerts): updated dependencies * feat(chalice): fixed recordings response recursion error * feat(assist): updated dependencies feat(sourcemaps-reader): upgraded dependencies * change(ui) - user event text change * fix(ui): fix events merging * fix(connector): handle db connection drop (#1223) * Added compatibility with SaaS, added reboot of connection if connection droped * Small fix * fix(backend): disabled debug log in http handler * fix(player): fix autopause on tabs * Updated python template to read messages with BatchMeta with old version (#1225) * change(ui) - user events text change * change(ui) - webhooks no content icon size * chore(backend): upgraded go to 1.19 and ClickHouse to 2.9.1 * fix(player): fix frustrations ingestion * fix(tracker): fix email detection performance * fix(tracker): fix email masking length * fix(player): fix fullview prop passing to children (live pl) * feat(chalice): reduce issues for replay (#1227) * change(ui) - bugreport modal title color * fix(ui) - elastic config validation rules * change(ui) - issue form and share popup titles * change(ui) - placeholder text change * change(ui) - filter user events text change * feat(chalice): include enforceSSO in signup status (#1228) * Updating kyverno * chore(cli): Override GH repo Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(helm): Update kafka chart Enable metrics and increased storage * change(ui) - enforce sso * Api v1.12.0 (#1230) * feat(chalice): include enforceSSO in signup status * feat(chalice): changed 1-time health-check * fix(helm): typo Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * change(ui) - support icon border * chore(helm): enable kafka jmx metrics Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * change(ui) - fetch details modal - no content text size * change(ui) - playback timemode alignment * fix(connector): fixed bug of cache dict size error (#1226) * change(ui) - text chante on create issue and share popups * change(ui) - share popup styles * change(ui) - user events visit event padding * feat(crons): include fastapi (#1231) * New env variable CLOUD (aws by default) (#1232) * feat(backend): added new env variable CLOUD (aws by default) * chore(backend): Adding env variable for CLOUD Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> Co-authored-by: rjshrjndrn <rjshrjndrn@gmail.com> * Compression worker (#1233) * feat(backend): added extra worker for session compression * feat(backend): debug logs * feat(backend): added compression ratio metric * feat(backend): reduced number of duplicate logs * feat(backend): rewrite workers managment * chore(minio): changed lifecycle rules to support delete-jobs (#1235) * fix(backend): correct compression ratio value * fix(backend): reduced ender tick duration * feat(backend): insert referrer to sessions table (#1237) * chore(cli): Adding separate query for ee cleanup Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(connector): Added checkpoints and sigterm handler (#1234) * fix(connector): fixed bug of cache dict size error * fix(connector): Added method to save state in s3 for redshift if sigterm arise * fix(connector): Added exit signal handler and checkpoint method * Added sslmode selection for connection to database, added use_ssl parameter for S3 connection * fix(cli): Override cli options (#1239) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(player): fix first 8 byte checker * fix(player): remove logs * Update .env.sample * fix(ui) - search init - wait for filters (#1241) * fix(player): fix first 8 byte checker * chore(cron): Adding missing deps Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(player): fix commit conflict * fix(backend): added Content-Encoding to CORS for http service * fix(backend): added COMPRESSION_THRESHOLD env variable to Dockerfile * fix(player): ensure that player is cleaned on unmount * chore(helm): Updating frontend image release (#1243) * Update README.md * feat(chalice): fixed trace payload parsing * feat(player): player file loader refactoring (#1203) * change(ui): refactor mob loading * refactor(player): split message loader into separate file, remove toast dependency out of player lib, fix types, fix inspector and screen context * refactor(player): simplify file loading, add safe error throws * refactor(player): move loading status changers to the end of the flow * change(ui) - assist call to use iceTransportPolicy all * change(ui) - removed errors route * chore(helm): enablig pg_stat for metrics Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * fix(tracker): fix time inputs capturing * change(ui) - antd dependency * fix(player): clear selection manger on clicks; display frustrations row on xray by default * fix(player): add option todisable network in iframes * refactor(cli): In old clusters kyverno upgrade won't work. So we'll have to upgrade OR only. Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * feat(tracker): new axios capturing; tracker 7.0.1 * feat(chalice) - feature flags (#1252) * feat(api) - feature flags - schema * feat(api) - feature flags - wip * feat(api) - feature flags * feat(api) - feature flags - set back root path * feat(api) - feature flags * feat(api) - feature flags * feat(api) - feature flags - review * feat(DB): feature flags DB structure * feat(chalice): feature flags permissions support feat(chalice): feature flags changed code * feat(chalice): feature flags add permissions to DB --------- Co-authored-by: Taha Yassine Kraiem <tahayk2@gmail.com> * [sourcemaps-reader] Azure blob storage support (#1259) * feat(sourcemaps-reader): implemented azure blob storage support for sourcemaps reader * feat(sourcemaps-reader): azure blob storage support - cleaned code --------- Co-authored-by: Taha Yassine Kraiem <tahayk2@gmail.com> * fix(player): fix selection manager styles and reset * fix(cli): KUBECONFIG PATH override (#1266) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * chore(cli): Adding info on which kubeconfig is getting used (#1261) Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> * feat(ui) - enforce pwd during signup (#1271) * fix(helm): SITE_URL injection * fix(player): hide empty index prop * change(repo): ignore precommit config * change(repo): precommit config * feat(chalice): faster projects response * fix(chalice): ignore SSO for testing * feat(chalice): added PyLint for dev purposes * feat(DB): support tab_id for all events * feat(chalice): removed PyLint * fix(chalice): include metadata in sessions exp search (#1291) (cherry picked from commit07dd9da820) * refactor(chalice): upgraded dependencies refactor(alerts): upgraded dependencies refactor(crons): upgraded dependencies * feat(DB): added tab_id in creation queries feat(DB): added user_city feat(DB): added user_state * feat(DB): added user_city feat(DB): added user_state * feat(DB): create index for user_city feat(DB): create index for user_state * feat(chalice): search sessions by user_city feat(chalice): search sessions by user_state * fix(chalice): install SSO dependencies --------- Signed-off-by: rjshrjndrn <rjshrjndrn@gmail.com> Co-authored-by: Alexander Zavorotynskiy <zavorotynskiy@pm.me> Co-authored-by: nick-delirium <nikita@openreplay.com> Co-authored-by: Rajesh Rajendran <rjshrjndrn@users.noreply.github.com> Co-authored-by: Shekar Siri <sshekarsiri@gmail.com> Co-authored-by: Alex Kaminskii <alex@openreplay.com> Co-authored-by: rjshrjndrn <rjshrjndrn@gmail.com> Co-authored-by: Mehdi Osman <estradino@users.noreply.github.com> Co-authored-by: MauricioGarciaS <47052044+MauricioGarciaS@users.noreply.github.com> Co-authored-by: Dayan Graham <d.graham50@hotmail.co.uk>
1445 lines
51 KiB
Python
1445 lines
51 KiB
Python
from enum import Enum
|
|
from typing import Optional, List, Union, Literal
|
|
|
|
from pydantic import BaseModel, Field, EmailStr, HttpUrl, root_validator, validator
|
|
|
|
from chalicelib.utils.TimeUTC import TimeUTC
|
|
|
|
|
|
def attribute_to_camel_case(snake_str):
|
|
components = snake_str.split("_")
|
|
return components[0] + ''.join(x.title() for x in components[1:])
|
|
|
|
|
|
def transform_email(email: str) -> str:
|
|
return email.lower().strip() if isinstance(email, str) else email
|
|
|
|
|
|
def remove_whitespace(value: str) -> str:
|
|
return " ".join(value.split()) if isinstance(value, str) else value
|
|
|
|
|
|
class _Grecaptcha(BaseModel):
|
|
g_recaptcha_response: Optional[str] = Field(None, alias='g-recaptcha-response')
|
|
|
|
|
|
class UserLoginSchema(_Grecaptcha):
|
|
email: EmailStr = Field(...)
|
|
password: str = Field(...)
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
|
|
class UserSignupSchema(UserLoginSchema):
|
|
fullname: str = Field(...)
|
|
organizationName: str = Field(...)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class EditUserSchema(BaseModel):
|
|
name: Optional[str] = Field(None)
|
|
email: Optional[EmailStr] = Field(None)
|
|
admin: Optional[bool] = Field(None)
|
|
|
|
_transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace)
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
|
|
class ForgetPasswordPayloadSchema(_Grecaptcha):
|
|
email: EmailStr = Field(...)
|
|
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
|
|
class EditUserPasswordSchema(BaseModel):
|
|
old_password: str = Field(...)
|
|
new_password: str = Field(...)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class UpdateTenantSchema(BaseModel):
|
|
name: Optional[str] = Field(None)
|
|
opt_out: Optional[bool] = Field(None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class CreateProjectSchema(BaseModel):
|
|
name: str = Field(default="my first project")
|
|
_transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
|
|
class CurrentAPIContext(BaseModel):
|
|
tenant_id: int = Field(...)
|
|
|
|
|
|
class CurrentContext(CurrentAPIContext):
|
|
user_id: int = Field(...)
|
|
email: EmailStr = Field(...)
|
|
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
|
|
class AddCollaborationSchema(BaseModel):
|
|
name: str = Field(...)
|
|
url: HttpUrl = Field(...)
|
|
_transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace)
|
|
_transform_url = validator('url', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
|
|
class EditCollaborationSchema(AddCollaborationSchema):
|
|
name: Optional[str] = Field(None)
|
|
|
|
|
|
class CreateNotificationSchema(BaseModel):
|
|
token: str = Field(...)
|
|
notifications: List = Field(...)
|
|
|
|
|
|
class _TimedSchema(BaseModel):
|
|
startTimestamp: int = Field(default=None)
|
|
endTimestamp: int = Field(default=None)
|
|
|
|
@root_validator
|
|
def time_validator(cls, values):
|
|
if values.get("startTimestamp") is not None and values.get("endTimestamp") is not None:
|
|
assert values.get("startTimestamp") < values.get("endTimestamp"), \
|
|
"endTimestamp must be greater than startTimestamp"
|
|
return values
|
|
|
|
|
|
class NotificationsViewSchema(_TimedSchema):
|
|
ids: Optional[List] = Field(default=[])
|
|
startTimestamp: Optional[int] = Field(default=None)
|
|
endTimestamp: Optional[int] = Field(default=None)
|
|
|
|
|
|
class GithubSchema(BaseModel):
|
|
token: str = Field(...)
|
|
|
|
|
|
class JiraSchema(GithubSchema):
|
|
username: str = Field(...)
|
|
url: HttpUrl = Field(...)
|
|
|
|
@validator('url')
|
|
def transform_url(cls, v: HttpUrl):
|
|
return HttpUrl.build(scheme=v.scheme.lower(), host=v.host.lower())
|
|
|
|
|
|
class CreateEditWebhookSchema(BaseModel):
|
|
webhookId: Optional[int] = Field(None)
|
|
endpoint: str = Field(...)
|
|
authHeader: Optional[str] = Field(None)
|
|
name: Optional[str] = Field(...)
|
|
_transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
|
|
class CreateMemberSchema(BaseModel):
|
|
userId: Optional[int] = Field(None)
|
|
name: str = Field(...)
|
|
email: EmailStr = Field(...)
|
|
admin: bool = Field(False)
|
|
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
_transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
|
|
class EditMemberSchema(EditUserSchema):
|
|
name: str = Field(...)
|
|
email: EmailStr = Field(...)
|
|
admin: bool = Field(False)
|
|
|
|
|
|
class EditPasswordByInvitationSchema(BaseModel):
|
|
invitation: str = Field(...)
|
|
passphrase: str = Field(..., alias="pass")
|
|
password: str = Field(...)
|
|
|
|
|
|
class AssignmentSchema(BaseModel):
|
|
assignee: str = Field(...)
|
|
description: str = Field(...)
|
|
title: str = Field(...)
|
|
issue_type: str = Field(...)
|
|
_transform_title = validator('title', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class CommentAssignmentSchema(BaseModel):
|
|
message: str = Field(...)
|
|
|
|
|
|
class IntegrationNotificationSchema(BaseModel):
|
|
comment: Optional[str] = Field(None)
|
|
|
|
|
|
class GdprSchema(BaseModel):
|
|
maskEmails: bool = Field(...)
|
|
sampleRate: int = Field(...)
|
|
maskNumbers: bool = Field(...)
|
|
defaultInputMode: str = Field(...)
|
|
|
|
|
|
class SampleRateSchema(BaseModel):
|
|
rate: int = Field(...)
|
|
captureAll: bool = Field(False)
|
|
|
|
|
|
class WeeklyReportConfigSchema(BaseModel):
|
|
weekly_report: bool = Field(True)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class DatadogSchema(BaseModel):
|
|
apiKey: str = Field(...)
|
|
applicationKey: str = Field(...)
|
|
|
|
|
|
class StackdriverSchema(BaseModel):
|
|
serviceAccountCredentials: str = Field(...)
|
|
logName: str = Field(...)
|
|
|
|
|
|
class NewrelicSchema(BaseModel):
|
|
applicationId: str = Field(...)
|
|
xQueryKey: str = Field(...)
|
|
region: str = Field(...)
|
|
|
|
|
|
class RollbarSchema(BaseModel):
|
|
accessToken: str = Field(...)
|
|
|
|
|
|
class BugsnagBasicSchema(BaseModel):
|
|
authorizationToken: str = Field(...)
|
|
|
|
|
|
class BugsnagSchema(BugsnagBasicSchema):
|
|
bugsnagProjectId: str = Field(...)
|
|
|
|
|
|
class CloudwatchBasicSchema(BaseModel):
|
|
awsAccessKeyId: str = Field(...)
|
|
awsSecretAccessKey: str = Field(...)
|
|
region: str = Field(...)
|
|
|
|
|
|
class CloudwatchSchema(CloudwatchBasicSchema):
|
|
logGroupName: str = Field(...)
|
|
|
|
|
|
class ElasticsearchBasicSchema(BaseModel):
|
|
host: str = Field(...)
|
|
port: int = Field(...)
|
|
apiKeyId: str = Field(...)
|
|
apiKey: str = Field(...)
|
|
|
|
|
|
class ElasticsearchSchema(ElasticsearchBasicSchema):
|
|
indexes: str = Field(...)
|
|
|
|
|
|
class SumologicSchema(BaseModel):
|
|
accessId: str = Field(...)
|
|
accessKey: str = Field(...)
|
|
region: str = Field(...)
|
|
|
|
|
|
class MetadataBasicSchema(BaseModel):
|
|
index: Optional[int] = Field(None)
|
|
key: str = Field(...)
|
|
_transform_key = validator('key', pre=True, allow_reuse=True)(remove_whitespace)
|
|
|
|
|
|
class MetadataListSchema(BaseModel):
|
|
list: List[MetadataBasicSchema] = Field(...)
|
|
|
|
|
|
class EmailPayloadSchema(BaseModel):
|
|
auth: str = Field(...)
|
|
email: EmailStr = Field(...)
|
|
link: str = Field(...)
|
|
message: str = Field(...)
|
|
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
|
|
class MemberInvitationPayloadSchema(BaseModel):
|
|
auth: str = Field(...)
|
|
email: EmailStr = Field(...)
|
|
invitation_link: str = Field(...)
|
|
client_id: str = Field(...)
|
|
sender_name: str = Field(...)
|
|
|
|
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class ErrorIdsPayloadSchema(BaseModel):
|
|
errors: List[str] = Field([])
|
|
|
|
|
|
class _AlertMessageSchema(BaseModel):
|
|
type: str = Field(...)
|
|
value: str = Field(...)
|
|
|
|
|
|
class AlertDetectionType(str, Enum):
|
|
percent = "percent"
|
|
change = "change"
|
|
|
|
|
|
class _AlertOptionSchema(BaseModel):
|
|
message: List[_AlertMessageSchema] = Field([])
|
|
currentPeriod: Literal[15, 30, 60, 120, 240, 1440] = Field(...)
|
|
previousPeriod: Literal[15, 30, 60, 120, 240, 1440] = Field(15)
|
|
lastNotification: Optional[int] = Field(None)
|
|
renotifyInterval: Optional[int] = Field(720)
|
|
|
|
|
|
class AlertColumn(str, Enum):
|
|
performance__dom_content_loaded__average = "performance.dom_content_loaded.average"
|
|
performance__first_meaningful_paint__average = "performance.first_meaningful_paint.average"
|
|
performance__page_load_time__average = "performance.page_load_time.average"
|
|
performance__dom_build_time__average = "performance.dom_build_time.average"
|
|
performance__speed_index__average = "performance.speed_index.average"
|
|
performance__page_response_time__average = "performance.page_response_time.average"
|
|
performance__ttfb__average = "performance.ttfb.average"
|
|
performance__time_to_render__average = "performance.time_to_render.average"
|
|
performance__image_load_time__average = "performance.image_load_time.average"
|
|
performance__request_load_time__average = "performance.request_load_time.average"
|
|
resources__load_time__average = "resources.load_time.average"
|
|
resources__missing__count = "resources.missing.count"
|
|
errors__4xx_5xx__count = "errors.4xx_5xx.count"
|
|
errors__4xx__count = "errors.4xx.count"
|
|
errors__5xx__count = "errors.5xx.count"
|
|
errors__javascript__impacted_sessions__count = "errors.javascript.impacted_sessions.count"
|
|
performance__crashes__count = "performance.crashes.count"
|
|
errors__javascript__count = "errors.javascript.count"
|
|
errors__backend__count = "errors.backend.count"
|
|
custom = "CUSTOM"
|
|
|
|
|
|
class MathOperator(str, Enum):
|
|
_equal = "="
|
|
_less = "<"
|
|
_greater = ">"
|
|
_less_eq = "<="
|
|
_greater_eq = ">="
|
|
|
|
|
|
class _AlertQuerySchema(BaseModel):
|
|
left: Union[AlertColumn, int] = Field(...)
|
|
right: float = Field(...)
|
|
operator: MathOperator = Field(...)
|
|
|
|
|
|
class AlertDetectionMethod(str, Enum):
|
|
threshold = "threshold"
|
|
change = "change"
|
|
|
|
|
|
class AlertSchema(BaseModel):
|
|
name: str = Field(...)
|
|
detection_method: AlertDetectionMethod = Field(...)
|
|
change: Optional[AlertDetectionType] = Field(default=AlertDetectionType.change)
|
|
description: Optional[str] = Field(None)
|
|
options: _AlertOptionSchema = Field(...)
|
|
query: _AlertQuerySchema = Field(...)
|
|
series_id: Optional[int] = Field(None)
|
|
|
|
@root_validator(pre=True)
|
|
def transform_alert(cls, values):
|
|
values["seriesId"] = None
|
|
if isinstance(values["query"]["left"], int):
|
|
values["seriesId"] = values["query"]["left"]
|
|
values["query"]["left"] = AlertColumn.custom
|
|
|
|
return values
|
|
|
|
@root_validator
|
|
def alert_validator(cls, values):
|
|
if values.get("query") is not None and values["query"].left == AlertColumn.custom:
|
|
assert values.get("series_id") is not None, "series_id should not be null for CUSTOM alert"
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class SourcemapUploadPayloadSchema(BaseModel):
|
|
urls: List[str] = Field(..., alias="URL")
|
|
|
|
|
|
class ErrorSource(str, Enum):
|
|
js_exception = "js_exception"
|
|
bugsnag = "bugsnag"
|
|
cloudwatch = "cloudwatch"
|
|
datadog = "datadog"
|
|
newrelic = "newrelic"
|
|
rollbar = "rollbar"
|
|
sentry = "sentry"
|
|
stackdriver = "stackdriver"
|
|
sumologic = "sumologic"
|
|
|
|
|
|
class EventType(str, Enum):
|
|
click = "click"
|
|
input = "input"
|
|
location = "location"
|
|
custom = "custom"
|
|
request = "request"
|
|
request_details = "fetch"
|
|
graphql = "graphql"
|
|
state_action = "stateAction"
|
|
error = "error"
|
|
click_ios = "clickIos"
|
|
input_ios = "inputIos"
|
|
view_ios = "viewIos"
|
|
custom_ios = "customIos"
|
|
request_ios = "requestIos"
|
|
error_ios = "errorIos"
|
|
|
|
|
|
class PerformanceEventType(str, Enum):
|
|
location_dom_complete = "domComplete"
|
|
location_largest_contentful_paint_time = "largestContentfulPaintTime"
|
|
time_between_events = "timeBetweenEvents"
|
|
location_ttfb = "ttfb"
|
|
location_avg_cpu_load = "avgCpuLoad"
|
|
location_avg_memory_usage = "avgMemoryUsage"
|
|
fetch_failed = "fetchFailed"
|
|
# fetch_duration = "FETCH_DURATION"
|
|
|
|
|
|
class FilterType(str, Enum):
|
|
user_os = "userOs"
|
|
user_browser = "userBrowser"
|
|
user_device = "userDevice"
|
|
user_country = "userCountry"
|
|
user_city = "userCity"
|
|
user_state = "userState"
|
|
user_id = "userId"
|
|
user_anonymous_id = "userAnonymousId"
|
|
referrer = "referrer"
|
|
rev_id = "revId"
|
|
# IOS
|
|
user_os_ios = "userOsIos"
|
|
user_device_ios = "userDeviceIos"
|
|
user_country_ios = "userCountryIos"
|
|
user_id_ios = "userIdIos"
|
|
user_anonymous_id_ios = "userAnonymousIdIos"
|
|
rev_id_ios = "revIdIos"
|
|
#
|
|
duration = "duration"
|
|
platform = "platform"
|
|
metadata = "metadata"
|
|
issue = "issue"
|
|
events_count = "eventsCount"
|
|
utm_source = "utmSource"
|
|
utm_medium = "utmMedium"
|
|
utm_campaign = "utmCampaign"
|
|
|
|
|
|
class SearchEventOperator(str, Enum):
|
|
_is = "is"
|
|
_is_any = "isAny"
|
|
_on = "on"
|
|
_on_any = "onAny"
|
|
_is_not = "isNot"
|
|
_is_undefined = "isUndefined"
|
|
_not_on = "notOn"
|
|
_contains = "contains"
|
|
_not_contains = "notContains"
|
|
_starts_with = "startsWith"
|
|
_ends_with = "endsWith"
|
|
|
|
|
|
class ClickEventExtraOperator(str, Enum):
|
|
_on_selector = "onSelector"
|
|
_on_text = "onText"
|
|
|
|
|
|
class IssueFilterOperator(str, Enum):
|
|
_on_selector = ClickEventExtraOperator._on_selector.value
|
|
|
|
|
|
class PlatformType(str, Enum):
|
|
mobile = "mobile"
|
|
desktop = "desktop"
|
|
tablet = "tablet"
|
|
|
|
|
|
class SearchEventOrder(str, Enum):
|
|
_then = "then"
|
|
_or = "or"
|
|
_and = "and"
|
|
|
|
|
|
class IssueType(str, Enum):
|
|
click_rage = 'click_rage'
|
|
dead_click = 'dead_click'
|
|
excessive_scrolling = 'excessive_scrolling'
|
|
bad_request = 'bad_request'
|
|
missing_resource = 'missing_resource'
|
|
memory = 'memory'
|
|
cpu = 'cpu'
|
|
slow_resource = 'slow_resource'
|
|
slow_page_load = 'slow_page_load'
|
|
crash = 'crash'
|
|
custom = 'custom'
|
|
js_exception = 'js_exception'
|
|
mouse_thrashing = 'mouse_thrashing'
|
|
|
|
|
|
class MetricFormatType(str, Enum):
|
|
session_count = 'sessionCount'
|
|
|
|
|
|
class __MixedSearchFilter(BaseModel):
|
|
is_event: bool = Field(...)
|
|
|
|
@root_validator(pre=True)
|
|
def remove_duplicate_values(cls, values):
|
|
if values.get("value") is not None:
|
|
if len(values["value"]) > 0 \
|
|
and (isinstance(values["value"][0], int) or isinstance(values["value"][0], dict)):
|
|
return values
|
|
values["value"] = list(set(values["value"]))
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class HttpMethod(str, Enum):
|
|
_get = 'GET'
|
|
_head = 'HEAD'
|
|
_post = 'POST'
|
|
_put = 'PUT'
|
|
_delete = 'DELETE'
|
|
_connect = 'CONNECT'
|
|
_option = 'OPTIONS'
|
|
_trace = 'TRACE'
|
|
_patch = 'PATCH'
|
|
|
|
|
|
class FetchFilterType(str, Enum):
|
|
_url = "fetchUrl" # FETCH_URL
|
|
_status_code = "fetchStatusCode" # FETCH_STATUS_CODE
|
|
_method = "fetchMethod" # FETCH_METHOD
|
|
_duration = "fetchDuration" # FETCH_DURATION
|
|
_request_body = "fetchRequestBody" # FETCH_REQUEST_BODY
|
|
_response_body = "fetchResponseBody" # FETCH_RESPONSE_BODY
|
|
|
|
|
|
class GraphqlFilterType(str, Enum):
|
|
_name = "graphqlName" # GRAPHQL_NAME
|
|
_method = "graphqlMethod" # GRAPHQL_METHOD
|
|
_request_body = "graphqlRequestBody" # GRAPHQL_REQUEST_BODY
|
|
_response_body = "graphqlResponseBody" # GRAPHQL_RESPONSE_BODY
|
|
|
|
|
|
class IssueFilterType(str, Enum):
|
|
_selector = "CLICK_SELECTOR"
|
|
|
|
|
|
class RequestGraphqlFilterSchema(BaseModel):
|
|
type: Union[FetchFilterType, GraphqlFilterType] = Field(...)
|
|
value: List[Union[int, str]] = Field(...)
|
|
operator: Union[SearchEventOperator, MathOperator] = Field(...)
|
|
|
|
|
|
class IssueFilterSchema(BaseModel):
|
|
type: IssueFilterType = Field(...)
|
|
value: List[str] = Field(...)
|
|
operator: IssueFilterOperator = Field(...)
|
|
|
|
|
|
class _SessionSearchEventRaw(__MixedSearchFilter):
|
|
is_event: bool = Field(default=True, const=True)
|
|
value: List[str] = Field(...)
|
|
type: Union[EventType, PerformanceEventType] = Field(...)
|
|
operator: Union[SearchEventOperator, ClickEventExtraOperator] = Field(...)
|
|
source: Optional[List[Union[ErrorSource, int, str]]] = Field(default=None)
|
|
sourceOperator: Optional[MathOperator] = Field(default=None)
|
|
filters: Optional[List[Union[RequestGraphqlFilterSchema, IssueFilterSchema]]] = Field(default=None)
|
|
|
|
@root_validator(pre=True)
|
|
def transform(cls, values):
|
|
if values.get("type") is None:
|
|
return values
|
|
values["type"] = {
|
|
"CLICK": EventType.click.value,
|
|
"INPUT": EventType.input.value,
|
|
"LOCATION": EventType.location.value,
|
|
"CUSTOM": EventType.custom.value,
|
|
"REQUEST": EventType.request.value,
|
|
"FETCH": EventType.request_details.value,
|
|
"GRAPHQL": EventType.graphql.value,
|
|
"STATEACTION": EventType.state_action.value,
|
|
"ERROR": EventType.error.value,
|
|
"CLICK_IOS": EventType.click_ios.value,
|
|
"INPUT_IOS": EventType.input_ios.value,
|
|
"VIEW_IOS": EventType.view_ios.value,
|
|
"CUSTOM_IOS": EventType.custom_ios.value,
|
|
"REQUEST_IOS": EventType.request_ios.value,
|
|
"ERROR_IOS": EventType.error_ios.value,
|
|
"DOM_COMPLETE": PerformanceEventType.location_dom_complete.value,
|
|
"LARGEST_CONTENTFUL_PAINT_TIME": PerformanceEventType.location_largest_contentful_paint_time.value,
|
|
"TIME_BETWEEN_EVENTS": PerformanceEventType.time_between_events.value,
|
|
"TTFB": PerformanceEventType.location_ttfb.value,
|
|
"AVG_CPU_LOAD": PerformanceEventType.location_avg_cpu_load.value,
|
|
"AVG_MEMORY_USAGE": PerformanceEventType.location_avg_memory_usage.value,
|
|
"FETCH_FAILED": PerformanceEventType.fetch_failed.value,
|
|
}.get(values["type"], values["type"])
|
|
return values
|
|
|
|
@root_validator
|
|
def event_validator(cls, values):
|
|
if isinstance(values.get("type"), PerformanceEventType):
|
|
if values.get("type") == PerformanceEventType.fetch_failed:
|
|
return values
|
|
# assert values.get("source") is not None, "source should not be null for PerformanceEventType"
|
|
# assert isinstance(values["source"], list) and len(values["source"]) > 0, \
|
|
# "source should not be empty for PerformanceEventType"
|
|
assert values.get("sourceOperator") is not None, \
|
|
"sourceOperator should not be null for PerformanceEventType"
|
|
if values["type"] == PerformanceEventType.time_between_events:
|
|
assert values["sourceOperator"] != MathOperator._equal, \
|
|
f"{MathOperator._equal} is not allowed for duration of {PerformanceEventType.time_between_events}"
|
|
assert len(values.get("value", [])) == 2, \
|
|
f"must provide 2 Events as value for {PerformanceEventType.time_between_events}"
|
|
assert isinstance(values["value"][0], _SessionSearchEventRaw) \
|
|
and isinstance(values["value"][1], _SessionSearchEventRaw), \
|
|
f"event should be of type _SessionSearchEventRaw for {PerformanceEventType.time_between_events}"
|
|
assert len(values["source"]) > 0 and isinstance(values["source"][0], int), \
|
|
f"source of type int is required for {PerformanceEventType.time_between_events}"
|
|
else:
|
|
assert "source" in values, f"source is required for {values.get('type')}"
|
|
assert isinstance(values["source"], list), f"source of type list is required for {values.get('type')}"
|
|
for c in values["source"]:
|
|
assert isinstance(c, int), f"source value should be of type int for {values.get('type')}"
|
|
elif values.get("type") == EventType.error and values.get("source") is None:
|
|
values["source"] = [ErrorSource.js_exception]
|
|
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}"
|
|
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}"
|
|
|
|
if isinstance(values.get("operator"), ClickEventExtraOperator):
|
|
assert values.get("type") == EventType.click, \
|
|
f"operator:{values['operator']} is only available for event-type: {EventType.click}"
|
|
return values
|
|
|
|
|
|
class _SessionSearchEventSchema(_SessionSearchEventRaw):
|
|
value: Union[List[Union[_SessionSearchEventRaw, str]], str] = Field(...)
|
|
|
|
|
|
def transform_old_FilterType(cls, values):
|
|
if values.get("type") is None:
|
|
return values
|
|
values["type"] = {
|
|
"USEROS": FilterType.user_os.value,
|
|
"USERBROWSER": FilterType.user_browser.value,
|
|
"USERDEVICE": FilterType.user_device.value,
|
|
"USERCOUNTRY": FilterType.user_country.value,
|
|
"USERID": FilterType.user_id.value,
|
|
"USERANONYMOUSID": FilterType.user_anonymous_id.value,
|
|
"REFERRER": FilterType.referrer.value,
|
|
"REVID": FilterType.rev_id.value,
|
|
"USEROS_IOS": FilterType.user_os_ios.value,
|
|
"USERDEVICE_IOS": FilterType.user_device_ios.value,
|
|
"USERCOUNTRY_IOS": FilterType.user_country_ios.value,
|
|
"USERID_IOS": FilterType.user_id_ios.value,
|
|
"USERANONYMOUSID_IOS": FilterType.user_anonymous_id_ios.value,
|
|
"REVID_IOS": FilterType.rev_id_ios.value,
|
|
"DURATION": FilterType.duration.value,
|
|
"PLATFORM": FilterType.platform.value,
|
|
"METADATA": FilterType.metadata.value,
|
|
"ISSUE": FilterType.issue.value,
|
|
"EVENTS_COUNT": FilterType.events_count.value,
|
|
"UTM_SOURCE": FilterType.utm_source.value,
|
|
"UTM_MEDIUM": FilterType.utm_medium.value,
|
|
"UTM_CAMPAIGN": FilterType.utm_campaign.value
|
|
}.get(values["type"], values["type"])
|
|
return values
|
|
|
|
|
|
class SessionSearchFilterSchema(__MixedSearchFilter):
|
|
is_event: bool = Field(False, const=False)
|
|
# TODO: remove this if there nothing broken from the UI
|
|
# value: Union[Optional[Union[IssueType, PlatformType, int, str]],
|
|
# Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...)
|
|
value: List[Union[IssueType, PlatformType, int, str]] = Field(default=[])
|
|
type: FilterType = Field(...)
|
|
operator: Union[SearchEventOperator, MathOperator] = Field(...)
|
|
source: Optional[Union[ErrorSource, str]] = Field(default=None)
|
|
filters: List[IssueFilterSchema] = Field(default=[])
|
|
|
|
transform = root_validator(pre=True, allow_reuse=True)(transform_old_FilterType)
|
|
|
|
@root_validator
|
|
def filter_validator(cls, values):
|
|
if values.get("type") == FilterType.metadata:
|
|
assert values.get("source") is not None and len(values["source"]) > 0, \
|
|
"must specify a valid 'source' for metadata filter"
|
|
elif values.get("type") == FilterType.issue:
|
|
for v in values.get("value"):
|
|
assert isinstance(v, IssueType), f"value should be of type IssueType for {values.get('type')} filter"
|
|
elif values.get("type") == FilterType.platform:
|
|
for v in values.get("value"):
|
|
assert isinstance(v, PlatformType), \
|
|
f"value should be of type PlatformType for {values.get('type')} filter"
|
|
elif values.get("type") == FilterType.events_count:
|
|
assert isinstance(values.get("operator"), MathOperator), \
|
|
f"operator should be of type MathOperator for {values.get('type')} filter"
|
|
for v in values.get("value"):
|
|
assert isinstance(v, int), f"value should be of type int for {values.get('type')} filter"
|
|
else:
|
|
assert isinstance(values.get("operator"), SearchEventOperator), \
|
|
f"operator should be of type SearchEventOperator for {values.get('type')} filter"
|
|
return values
|
|
|
|
|
|
class _PaginatedSchema(BaseModel):
|
|
limit: int = Field(default=200, gt=0, le=200)
|
|
page: int = Field(default=1, gt=0)
|
|
|
|
|
|
class SortOrderType(str, Enum):
|
|
asc = "ASC"
|
|
desc = "DESC"
|
|
|
|
|
|
class SessionsSearchPayloadSchema(_PaginatedSchema):
|
|
events: List[_SessionSearchEventSchema] = Field([])
|
|
filters: List[SessionSearchFilterSchema] = Field([])
|
|
startDate: int = Field(None)
|
|
endDate: int = Field(None)
|
|
sort: str = Field(default="startTs")
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
events_order: Optional[SearchEventOrder] = Field(default=SearchEventOrder._then)
|
|
group_by_user: bool = Field(default=False)
|
|
bookmarked: bool = Field(default=False)
|
|
|
|
@root_validator(pre=True)
|
|
def transform_order(cls, values):
|
|
if values.get("sort") is None:
|
|
values["sort"] = "startTs"
|
|
|
|
if values.get("order") is None:
|
|
values["order"] = SortOrderType.desc
|
|
else:
|
|
values["order"] = values["order"].upper()
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class FlatSessionsSearch(BaseModel):
|
|
events: Optional[List[_SessionSearchEventSchema]] = Field([])
|
|
filters: List[Union[SessionSearchFilterSchema, _SessionSearchEventSchema]] = Field([])
|
|
|
|
@root_validator(pre=True)
|
|
def flat_to_original(cls, values):
|
|
# in case the old search body was passed
|
|
if len(values.get("events", [])) > 0:
|
|
for v in values["events"]:
|
|
v["isEvent"] = True
|
|
for v in values.get("filters", []):
|
|
v["isEvent"] = False
|
|
else:
|
|
n_filters = []
|
|
n_events = []
|
|
for v in values.get("filters", []):
|
|
if v.get("isEvent"):
|
|
n_events.append(v)
|
|
else:
|
|
v["isEvent"] = False
|
|
n_filters.append(v)
|
|
values["events"] = n_events
|
|
values["filters"] = n_filters
|
|
return values
|
|
|
|
|
|
class FlatSessionsSearchPayloadSchema(FlatSessionsSearch, SessionsSearchPayloadSchema):
|
|
pass
|
|
|
|
|
|
class SessionsSearchCountSchema(FlatSessionsSearchPayloadSchema):
|
|
# class SessionsSearchCountSchema(SessionsSearchPayloadSchema):
|
|
sort: Optional[str] = Field(default=None)
|
|
order: Optional[str] = Field(default=None)
|
|
|
|
|
|
class FunnelSearchPayloadSchema(FlatSessionsSearchPayloadSchema):
|
|
# class FunnelSearchPayloadSchema(SessionsSearchPayloadSchema):
|
|
range_value: Optional[str] = Field(None)
|
|
sort: Optional[str] = Field(None)
|
|
order: Optional[str] = Field(None)
|
|
events_order: Optional[SearchEventOrder] = Field(default=SearchEventOrder._then, const=True)
|
|
group_by_user: Optional[bool] = Field(default=False, const=True)
|
|
rangeValue: Optional[str] = Field(None)
|
|
|
|
@root_validator(pre=True)
|
|
def enforce_default_values(cls, values):
|
|
values["eventsOrder"] = SearchEventOrder._then
|
|
values["groupByUser"] = False
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class FunnelSchema(BaseModel):
|
|
name: str = Field(...)
|
|
filter: FunnelSearchPayloadSchema = Field([])
|
|
is_public: bool = Field(default=False)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class FunnelInsightsPayloadSchema(FlatSessionsSearchPayloadSchema):
|
|
# class FunnelInsightsPayloadSchema(SessionsSearchPayloadSchema):
|
|
sort: Optional[str] = Field(None)
|
|
order: Optional[str] = Field(None)
|
|
events_order: Optional[SearchEventOrder] = Field(default=SearchEventOrder._then, const=True)
|
|
group_by_user: Optional[bool] = Field(default=False, const=True)
|
|
rangeValue: Optional[str] = Field(None)
|
|
|
|
|
|
class ErrorStatus(str, Enum):
|
|
all = 'all'
|
|
unresolved = 'unresolved'
|
|
resolved = 'resolved'
|
|
ignored = 'ignored'
|
|
|
|
|
|
class ErrorSort(str, Enum):
|
|
occurrence = 'occurrence'
|
|
users_count = 'users'
|
|
sessions_count = 'sessions'
|
|
|
|
|
|
class SearchErrorsSchema(FlatSessionsSearchPayloadSchema):
|
|
sort: ErrorSort = Field(default=ErrorSort.occurrence)
|
|
density: Optional[int] = Field(7)
|
|
status: Optional[ErrorStatus] = Field(default=ErrorStatus.all)
|
|
query: Optional[str] = Field(default=None)
|
|
|
|
|
|
class ProductAnalyticsFilterType(str, Enum):
|
|
event_type = 'eventType'
|
|
start_point = 'startPoint'
|
|
user_id = FilterType.user_id.value
|
|
|
|
|
|
class ProductAnalyticsEventType(str, Enum):
|
|
click = EventType.click.value
|
|
input = EventType.input.value
|
|
location = EventType.location.value
|
|
custom_event = EventType.custom.value
|
|
|
|
|
|
class ProductAnalyticsFilter(BaseModel):
|
|
type: ProductAnalyticsFilterType = Field(...)
|
|
operator: Union[SearchEventOperator, ClickEventExtraOperator] = Field(...)
|
|
value: List[Union[ProductAnalyticsEventType | str]] = Field(...)
|
|
|
|
@root_validator
|
|
def validator(cls, values):
|
|
if values.get("type") == ProductAnalyticsFilterType.event_type:
|
|
assert values.get("value") is not None and len(values["value"]) > 0, \
|
|
f"value must be provided for type:{ProductAnalyticsFilterType.event_type}"
|
|
assert isinstance(values["value"][0], ProductAnalyticsEventType), \
|
|
f"value must be of type {ProductAnalyticsEventType} for type:{ProductAnalyticsFilterType.event_type}"
|
|
|
|
return values
|
|
|
|
|
|
class PathAnalysisSchema(_TimedSchema):
|
|
startTimestamp: int = Field(TimeUTC.now(delta_days=-1))
|
|
endTimestamp: int = Field(TimeUTC.now())
|
|
density: int = Field(7)
|
|
filters: List[ProductAnalyticsFilter] = Field(default=[])
|
|
type: Optional[str] = Field(default=None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class AssistSearchPayloadSchema(BaseModel):
|
|
filters: List[dict] = Field([])
|
|
|
|
|
|
class SentrySchema(BaseModel):
|
|
projectSlug: str = Field(...)
|
|
organizationSlug: str = Field(...)
|
|
token: str = Field(...)
|
|
|
|
|
|
class MobileSignPayloadSchema(BaseModel):
|
|
keys: List[str] = Field(...)
|
|
|
|
|
|
class CardSeriesFilterSchema(SearchErrorsSchema):
|
|
startDate: Optional[int] = Field(default=None)
|
|
endDate: Optional[int] = Field(default=None)
|
|
sort: Optional[str] = Field(default=None)
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
group_by_user: Optional[bool] = Field(default=False, const=True)
|
|
|
|
|
|
class CardSeriesSchema(BaseModel):
|
|
series_id: Optional[int] = Field(None)
|
|
name: Optional[str] = Field(None)
|
|
index: Optional[int] = Field(None)
|
|
filter: Optional[Union[CardSeriesFilterSchema | PathAnalysisSchema]] = Field(default=None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class MetricTimeseriesViewType(str, Enum):
|
|
line_chart = "lineChart"
|
|
progress = "progress"
|
|
area_chart = "areaChart"
|
|
|
|
|
|
class MetricTableViewType(str, Enum):
|
|
table = "table"
|
|
pie_chart = "pieChart"
|
|
|
|
|
|
class MetricOtherViewType(str, Enum):
|
|
other_chart = "chart"
|
|
list_chart = "list"
|
|
|
|
|
|
class MetricType(str, Enum):
|
|
timeseries = "timeseries"
|
|
table = "table"
|
|
funnel = "funnel"
|
|
errors = "errors"
|
|
performance = "performance"
|
|
resources = "resources"
|
|
web_vital = "webVitals"
|
|
pathAnalysis = "pathAnalysis"
|
|
retention = "retention"
|
|
stickiness = "stickiness"
|
|
click_map = "clickMap"
|
|
insights = "insights"
|
|
|
|
|
|
class MetricOfErrors(str, Enum):
|
|
calls_errors = "callsErrors" # calls_errors
|
|
domains_errors_4xx = "domainsErrors4xx" # domains_errors_4xx
|
|
domains_errors_5xx = "domainsErrors5xx" # domains_errors_5xx
|
|
errors_per_domains = "errorsPerDomains" # errors_per_domains
|
|
errors_per_type = "errorsPerType" # errors_per_type
|
|
impacted_sessions_by_js_errors = "impactedSessionsByJsErrors" # impacted_sessions_by_js_errors
|
|
resources_by_party = "resourcesByParty" # resources_by_party
|
|
|
|
|
|
class MetricOfPerformance(str, Enum):
|
|
cpu = "cpu" # cpu
|
|
crashes = "crashes" # crashes
|
|
fps = "fps" # fps
|
|
impacted_sessions_by_slow_pages = "impactedSessionsBySlowPages" # impacted_sessions_by_slow_pages
|
|
memory_consumption = "memoryConsumption" # memory_consumption
|
|
pages_dom_buildtime = "pagesDomBuildtime" # pages_dom_buildtime
|
|
pages_response_time = "pagesResponseTime" # pages_response_time
|
|
pages_response_time_distribution = "pagesResponseTimeDistribution" # pages_response_time_distribution
|
|
resources_vs_visually_complete = "resourcesVsVisuallyComplete" # resources_vs_visually_complete
|
|
sessions_per_browser = "sessionsPerBrowser" # sessions_per_browser
|
|
slowest_domains = "slowestDomains" # slowest_domains
|
|
speed_location = "speedLocation" # speed_location
|
|
time_to_render = "timeToRender" # time_to_render
|
|
|
|
|
|
class MetricOfResources(str, Enum):
|
|
missing_resources = "missingResources" # missing_resources
|
|
resources_count_by_type = "resourcesCountByType" # resources_count_by_type
|
|
resources_loading_time = "resourcesLoadingTime" # resources_loading_time
|
|
resource_type_vs_response_end = "resourceTypeVsResponseEnd" # resource_type_vs_response_end
|
|
slowest_resources = "slowestResources" # slowest_resources
|
|
|
|
|
|
class MetricOfWebVitals(str, Enum):
|
|
avg_cpu = "avgCpu" # avg_cpu
|
|
avg_dom_content_loaded = "avgDomContentLoaded" # avg_dom_content_loaded
|
|
avg_dom_content_load_start = "avgDomContentLoadStart" # avg_dom_content_load_start
|
|
avg_first_contentful_pixel = "avgFirstContentfulPixel" # avg_first_contentful_pixel
|
|
avg_first_paint = "avgFirstPaint" # avg_first_paint
|
|
avg_fps = "avgFps" # avg_fps
|
|
avg_image_load_time = "avgImageLoadTime" # avg_image_load_time
|
|
avg_page_load_time = "avgPageLoadTime" # avg_page_load_time
|
|
avg_pages_dom_buildtime = "avgPagesDomBuildtime" # avg_pages_dom_buildtime
|
|
avg_pages_response_time = "avgPagesResponseTime" # avg_pages_response_time
|
|
avg_request_load_time = "avgRequestLoadTime" # avg_request_load_time
|
|
avg_response_time = "avgResponseTime" # avg_response_time
|
|
avg_session_duration = "avgSessionDuration" # avg_session_duration
|
|
avg_till_first_byte = "avgTillFirstByte" # avg_till_first_byte
|
|
avg_time_to_interactive = "avgTimeToInteractive" # avg_time_to_interactive
|
|
avg_time_to_render = "avgTimeToRender" # avg_time_to_render
|
|
avg_used_js_heap_size = "avgUsedJsHeapSize" # avg_used_js_heap_size
|
|
avg_visited_pages = "avgVisitedPages" # avg_visited_pages
|
|
count_requests = "countRequests" # count_requests
|
|
count_sessions = "countSessions" # count_sessions
|
|
|
|
|
|
class MetricOfTable(str, Enum):
|
|
user_os = FilterType.user_os.value
|
|
user_browser = FilterType.user_browser.value
|
|
user_device = FilterType.user_device.value
|
|
user_country = FilterType.user_country.value
|
|
user_city = FilterType.user_city.value
|
|
user_state = FilterType.user_state.value
|
|
user_id = FilterType.user_id.value
|
|
issues = FilterType.issue.value
|
|
visited_url = "location"
|
|
sessions = "sessions"
|
|
errors = "jsException"
|
|
|
|
|
|
class MetricOfTimeseries(str, Enum):
|
|
session_count = "sessionCount"
|
|
|
|
|
|
class MetricOfClickMap(str, Enum):
|
|
click_map_url = "clickMapUrl"
|
|
|
|
|
|
class CardSessionsSchema(FlatSessionsSearch, _PaginatedSchema, _TimedSchema):
|
|
startTimestamp: int = Field(TimeUTC.now(-7))
|
|
endTimestamp: int = Field(TimeUTC.now())
|
|
series: List[CardSeriesSchema] = Field(default=[])
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class CardChartSchema(CardSessionsSchema):
|
|
density: int = Field(7)
|
|
|
|
|
|
class CardConfigSchema(BaseModel):
|
|
col: Optional[int] = Field(...)
|
|
row: Optional[int] = Field(default=2)
|
|
position: Optional[int] = Field(default=0)
|
|
|
|
|
|
class __CardSchema(BaseModel):
|
|
name: Optional[str] = Field(...)
|
|
is_public: bool = Field(default=True)
|
|
default_config: CardConfigSchema = Field(..., alias="config")
|
|
thumbnail: Optional[str] = Field(default=None)
|
|
metric_format: Optional[MetricFormatType] = Field(default=None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class CardSchema(__CardSchema, CardChartSchema):
|
|
view_type: Union[MetricTimeseriesViewType, \
|
|
MetricTableViewType, MetricOtherViewType] = Field(...)
|
|
metric_type: MetricType = Field(...)
|
|
metric_of: Union[MetricOfTimeseries, MetricOfTable, MetricOfErrors, \
|
|
MetricOfPerformance, MetricOfResources, MetricOfWebVitals, \
|
|
MetricOfClickMap] = Field(default=MetricOfTable.user_id)
|
|
metric_value: List[IssueType] = Field(default=[])
|
|
is_template: bool = Field(default=False)
|
|
|
|
# This is used to handle wrong values sent by the UI
|
|
@root_validator(pre=True)
|
|
def transform(cls, values):
|
|
values["isTemplate"] = values.get("metricType") in [MetricType.errors, MetricType.performance,
|
|
MetricType.resources, MetricType.web_vital]
|
|
if values.get("metricType") == MetricType.timeseries \
|
|
or values.get("metricType") == MetricType.table \
|
|
and values.get("metricOf") != MetricOfTable.issues:
|
|
values["metricValue"] = []
|
|
|
|
if values.get("metricType") in [MetricType.funnel, MetricType.pathAnalysis] and \
|
|
values.get("series") is not None and len(values["series"]) > 0:
|
|
values["series"] = [values["series"][0]]
|
|
elif values.get("metricType") not in [MetricType.table,
|
|
MetricType.timeseries,
|
|
MetricType.insights,
|
|
MetricType.click_map,
|
|
MetricType.funnel,
|
|
MetricType.pathAnalysis] \
|
|
and values.get("series") is not None and len(values["series"]) > 0:
|
|
values["series"] = []
|
|
|
|
return values
|
|
|
|
@root_validator
|
|
def restrictions(cls, values):
|
|
assert values.get("metric_type") != MetricType.insights, f"metricType:{MetricType.insights} not supported yet"
|
|
return values
|
|
|
|
@root_validator
|
|
def validator(cls, values):
|
|
if values.get("metric_type") == MetricType.timeseries:
|
|
assert isinstance(values.get("view_type"), MetricTimeseriesViewType), \
|
|
f"viewType must be of type {MetricTimeseriesViewType} for metricType:{MetricType.timeseries}"
|
|
assert isinstance(values.get("metric_of"), MetricOfTimeseries), \
|
|
f"metricOf must be of type {MetricOfTimeseries} for metricType:{MetricType.timeseries}"
|
|
elif values.get("metric_type") == MetricType.table:
|
|
assert isinstance(values.get("view_type"), MetricTableViewType), \
|
|
f"viewType must be of type {MetricTableViewType} for metricType:{MetricType.table}"
|
|
assert isinstance(values.get("metric_of"), MetricOfTable), \
|
|
f"metricOf must be of type {MetricOfTable} for metricType:{MetricType.table}"
|
|
if values.get("metric_of") in (MetricOfTable.sessions, MetricOfTable.errors):
|
|
assert values.get("view_type") == MetricTableViewType.table, \
|
|
f"viewType must be '{MetricTableViewType.table}' for metricOf:{values['metric_of']}"
|
|
if values.get("metric_of") != MetricOfTable.issues:
|
|
assert values.get("metric_value") is None or len(values.get("metric_value")) == 0, \
|
|
f"metricValue is only available for metricOf:{MetricOfTable.issues}"
|
|
elif values.get("metric_type") == MetricType.funnel:
|
|
pass
|
|
# allow UI sot send empty series for funnel
|
|
# assert len(values["series"]) == 1, f"must have only 1 series for metricType:{MetricType.funnel}"
|
|
# ignore this for now, let the UI send whatever he wants for metric_of
|
|
# assert isinstance(values.get("metric_of"), MetricOfTimeseries), \
|
|
# f"metricOf must be of type {MetricOfTimeseries} for metricType:{MetricType.funnel}"
|
|
elif values.get("metric_type") == MetricType.pathAnalysis:
|
|
pass
|
|
else:
|
|
if values.get("metric_type") == MetricType.errors:
|
|
assert isinstance(values.get("metric_of"), MetricOfErrors), \
|
|
f"metricOf must be of type {MetricOfErrors} for metricType:{MetricType.errors}"
|
|
elif values.get("metric_type") == MetricType.performance:
|
|
assert isinstance(values.get("metric_of"), MetricOfPerformance), \
|
|
f"metricOf must be of type {MetricOfPerformance} for metricType:{MetricType.performance}"
|
|
elif values.get("metric_type") == MetricType.resources:
|
|
assert isinstance(values.get("metric_of"), MetricOfResources), \
|
|
f"metricOf must be of type {MetricOfResources} for metricType:{MetricType.resources}"
|
|
elif values.get("metric_type") == MetricType.web_vital:
|
|
assert isinstance(values.get("metric_of"), MetricOfWebVitals), \
|
|
f"metricOf must be of type {MetricOfWebVitals} for metricType:{MetricType.web_vital}"
|
|
elif values.get("metric_type") == MetricType.click_map:
|
|
assert isinstance(values.get("metric_of"), MetricOfClickMap), \
|
|
f"metricOf must be of type {MetricOfClickMap} for metricType:{MetricType.click_map}"
|
|
# Allow only LOCATION events for clickMap
|
|
for s in values.get("series", []):
|
|
for f in s.filter.events:
|
|
assert f.type == EventType.location, f"only events of type:{EventType.location} are allowed for metricOf:{MetricType.click_map}"
|
|
|
|
assert isinstance(values.get("view_type"), MetricOtherViewType), \
|
|
f"viewType must be 'chart|list' for metricOf:{values.get('metric_of')}"
|
|
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class CardUpdateSeriesSchema(CardSeriesSchema):
|
|
series_id: Optional[int] = Field(None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class UpdateCardSchema(CardSchema):
|
|
series: List[CardUpdateSeriesSchema] = Field(...)
|
|
|
|
|
|
class UpdateCustomMetricsStatusSchema(BaseModel):
|
|
active: bool = Field(...)
|
|
|
|
|
|
class SavedSearchSchema(FunnelSchema):
|
|
filter: FlatSessionsSearchPayloadSchema = Field([])
|
|
|
|
|
|
class CreateDashboardSchema(BaseModel):
|
|
name: str = Field(..., min_length=1)
|
|
description: Optional[str] = Field(default='')
|
|
is_public: bool = Field(default=False)
|
|
is_pinned: bool = Field(default=False)
|
|
metrics: Optional[List[int]] = Field(default=[])
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class EditDashboardSchema(CreateDashboardSchema):
|
|
is_public: Optional[bool] = Field(default=None)
|
|
is_pinned: Optional[bool] = Field(default=None)
|
|
|
|
|
|
class UpdateWidgetPayloadSchema(BaseModel):
|
|
config: dict = Field(default={})
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class AddWidgetToDashboardPayloadSchema(UpdateWidgetPayloadSchema):
|
|
metric_id: int = Field(...)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class TemplatePredefinedUnits(str, Enum):
|
|
millisecond = "ms"
|
|
second = "s"
|
|
minute = "min"
|
|
memory = "mb"
|
|
frame = "f/s"
|
|
percentage = "%"
|
|
count = "count"
|
|
|
|
|
|
class LiveFilterType(str, Enum):
|
|
user_os = FilterType.user_os.value
|
|
user_browser = FilterType.user_browser.value
|
|
user_device = FilterType.user_device.value
|
|
user_country = FilterType.user_country.value
|
|
user_id = FilterType.user_id.value
|
|
user_anonymous_id = FilterType.user_anonymous_id.value
|
|
rev_id = FilterType.rev_id.value
|
|
platform = FilterType.platform.value
|
|
page_title = "pageTitle"
|
|
session_id = "sessionId"
|
|
metadata = FilterType.metadata.value
|
|
user_UUID = "userUuid"
|
|
tracker_version = "trackerVersion"
|
|
user_browser_version = "userBrowserVersion"
|
|
user_device_type = "userDeviceType"
|
|
|
|
|
|
class LiveSessionSearchFilterSchema(BaseModel):
|
|
value: Union[List[str], str] = Field(...)
|
|
type: LiveFilterType = Field(...)
|
|
source: Optional[str] = Field(default=None)
|
|
operator: Literal[SearchEventOperator._is, \
|
|
SearchEventOperator._contains] = Field(default=SearchEventOperator._contains)
|
|
|
|
transform = root_validator(pre=True, allow_reuse=True)(transform_old_FilterType)
|
|
|
|
@root_validator
|
|
def validator(cls, values):
|
|
if values.get("type") is not None and values["type"] == LiveFilterType.metadata:
|
|
assert values.get("source") is not None, "source should not be null for METADATA type"
|
|
assert len(values.get("source")) > 0, "source should not be empty for METADATA type"
|
|
return values
|
|
|
|
|
|
class LiveSessionsSearchPayloadSchema(_PaginatedSchema):
|
|
filters: List[LiveSessionSearchFilterSchema] = Field([])
|
|
sort: Union[LiveFilterType, str] = Field(default="TIMESTAMP")
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
|
|
@root_validator(pre=True)
|
|
def transform(cls, values):
|
|
if values.get("order") is not None:
|
|
values["order"] = values["order"].upper()
|
|
if values.get("filters") is not None:
|
|
i = 0
|
|
while i < len(values["filters"]):
|
|
if values["filters"][i]["value"] is None or len(values["filters"][i]["value"]) == 0:
|
|
del values["filters"][i]
|
|
else:
|
|
i += 1
|
|
for i in values["filters"]:
|
|
if i.get("type") == LiveFilterType.platform:
|
|
i["type"] = LiveFilterType.user_device_type
|
|
if values.get("sort") is not None:
|
|
if values["sort"].lower() == "startts":
|
|
values["sort"] = "TIMESTAMP"
|
|
return values
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class IntegrationType(str, Enum):
|
|
github = "GITHUB"
|
|
jira = "JIRA"
|
|
slack = "SLACK"
|
|
ms_teams = "MSTEAMS"
|
|
sentry = "SENTRY"
|
|
bugsnag = "BUGSNAG"
|
|
rollbar = "ROLLBAR"
|
|
elasticsearch = "ELASTICSEARCH"
|
|
datadog = "DATADOG"
|
|
sumologic = "SUMOLOGIC"
|
|
stackdriver = "STACKDRIVER"
|
|
cloudwatch = "CLOUDWATCH"
|
|
newrelic = "NEWRELIC"
|
|
|
|
|
|
class SearchNoteSchema(_PaginatedSchema):
|
|
sort: str = Field(default="createdAt")
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
tags: Optional[List[str]] = Field(default=[])
|
|
shared_only: bool = Field(default=False)
|
|
mine_only: bool = Field(default=False)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class SessionNoteSchema(BaseModel):
|
|
message: str = Field(..., min_length=2)
|
|
tag: Optional[str] = Field(default=None)
|
|
timestamp: int = Field(default=-1)
|
|
is_public: bool = Field(default=False)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class SessionUpdateNoteSchema(SessionNoteSchema):
|
|
message: Optional[str] = Field(default=None, min_length=2)
|
|
timestamp: Optional[int] = Field(default=None, ge=-1)
|
|
is_public: Optional[bool] = Field(default=None)
|
|
|
|
@root_validator
|
|
def validator(cls, values):
|
|
assert len(values.keys()) > 0, "at least 1 attribute should be provided for update"
|
|
c = 0
|
|
for v in values.values():
|
|
if v is not None and (not isinstance(v, str) or len(v) > 0):
|
|
c += 1
|
|
break
|
|
assert c > 0, "at least 1 value should be provided for update"
|
|
return values
|
|
|
|
|
|
class WebhookType(str, Enum):
|
|
webhook = "webhook"
|
|
slack = "slack"
|
|
email = "email"
|
|
msteams = "msteams"
|
|
|
|
|
|
class SearchCardsSchema(_PaginatedSchema):
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
shared_only: bool = Field(default=False)
|
|
mine_only: bool = Field(default=False)
|
|
query: Optional[str] = Field(default=None)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class _ClickMapSearchEventRaw(_SessionSearchEventRaw):
|
|
type: Literal[EventType.location] = Field(...)
|
|
|
|
|
|
class FlatClickMapSessionsSearch(SessionsSearchPayloadSchema):
|
|
events: Optional[List[_ClickMapSearchEventRaw]] = Field([])
|
|
filters: List[Union[SessionSearchFilterSchema, _ClickMapSearchEventRaw]] = Field([])
|
|
|
|
@root_validator(pre=True)
|
|
def transform(cls, values):
|
|
for f in values.get("filters", []):
|
|
if f.get("type") == FilterType.duration:
|
|
return values
|
|
values["filters"] = values.get("filters", [])
|
|
values["filters"].append({"value": [5000], "type": FilterType.duration,
|
|
"operator": SearchEventOperator._is, "filters": []})
|
|
return values
|
|
|
|
@root_validator()
|
|
def flat_to_original(cls, values):
|
|
if len(values["events"]) > 0:
|
|
return values
|
|
n_filters = []
|
|
n_events = []
|
|
for v in values.get("filters", []):
|
|
if isinstance(v, _ClickMapSearchEventRaw):
|
|
n_events.append(v)
|
|
else:
|
|
n_filters.append(v)
|
|
values["events"] = n_events
|
|
values["filters"] = n_filters
|
|
return values
|
|
|
|
|
|
class IssueAdvancedFilter(BaseModel):
|
|
type: IssueFilterType = Field(default=IssueFilterType._selector)
|
|
value: List[str] = Field(default=[])
|
|
operator: SearchEventOperator = Field(default=SearchEventOperator._is)
|
|
|
|
|
|
class ClickMapFilterSchema(BaseModel):
|
|
value: List[Literal[IssueType.click_rage, IssueType.dead_click]] = Field(default=[])
|
|
type: Literal[FilterType.issue] = Field(...)
|
|
operator: Literal[SearchEventOperator._is, MathOperator._equal] = Field(...)
|
|
# source: Optional[Union[ErrorSource, str]] = Field(default=None)
|
|
filters: List[IssueAdvancedFilter] = Field(default=[])
|
|
|
|
|
|
class GetHeatmapPayloadSchema(BaseModel):
|
|
startDate: int = Field(TimeUTC.now(delta_days=-30))
|
|
endDate: int = Field(TimeUTC.now())
|
|
url: str = Field(...)
|
|
# issues: List[Literal[IssueType.click_rage, IssueType.dead_click]] = Field(default=[])
|
|
filters: List[ClickMapFilterSchema] = Field(default=[])
|
|
click_rage: bool = Field(default=False)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class FeatureFlagCondition(BaseModel):
|
|
condition_id: Optional[int] = Field(default=None)
|
|
name: str = Field(...)
|
|
rollout_percentage: Optional[int] = Field(default=0)
|
|
filters: List[dict] = Field(default=[])
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class SearchFlagsSchema(_PaginatedSchema):
|
|
limit: int = Field(default=15, gt=0, le=200)
|
|
user_id: Optional[int] = Field(default=None)
|
|
order: SortOrderType = Field(default=SortOrderType.desc)
|
|
query: Optional[str] = Field(default=None)
|
|
is_active: bool = Field(default=True)
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|
|
|
|
|
|
class FeatureFlagType(str, Enum):
|
|
single_variant = "single"
|
|
multi_variant = "multi"
|
|
|
|
|
|
class FeatureFlagSchema(BaseModel):
|
|
name: str = Field(...)
|
|
flag_key: str = Field(...)
|
|
description: Optional[str] = Field(None)
|
|
flag_type: FeatureFlagType = Field(default=FeatureFlagType.single_variant)
|
|
is_persist: Optional[bool] = Field(default=False)
|
|
is_active: Optional[bool] = Field(default=True)
|
|
conditions: List[FeatureFlagCondition] = Field(default=[])
|
|
|
|
class Config:
|
|
alias_generator = attribute_to_camel_case
|