openreplay/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql
Kraiem Taha Yassine a5ec35b270
Api v1.13.0 (#1310)
* 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 commit c947e48d99.

* 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 commit 07dd9da820)

* 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>
2023-06-07 13:19:14 +02:00

1290 lines
No EOL
79 KiB
PL/PgSQL

BEGIN;
CREATE SCHEMA IF NOT EXISTS events_common;
CREATE SCHEMA IF NOT EXISTS events;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE OR REPLACE FUNCTION openreplay_version()
RETURNS text AS
$$
SELECT 'v1.13.0-ee'
$$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION generate_api_key(length integer) RETURNS text AS
$$
declare
chars text[] := '{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}';
result text := '';
i integer := 0;
begin
if length < 0 then
raise exception 'Given length cannot be less than 0';
end if;
for i in 1..length
loop
result := result || chars[1 + random() * (array_length(chars, 1) - 1)];
end loop;
return result;
end;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION events.funnel(steps integer[], m integer) RETURNS boolean AS
$$
DECLARE
step integer;
c integer := 0;
BEGIN
FOREACH step IN ARRAY steps
LOOP
IF step + c = 0 THEN
IF c = 0 THEN
RETURN false;
END IF;
c := 0;
CONTINUE;
END IF;
IF c + 1 = step THEN
c := step;
END IF;
END LOOP;
RETURN c = m;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION notify_integration() RETURNS trigger AS
$$
BEGIN
IF NEW IS NULL THEN
PERFORM pg_notify('integration',
jsonb_build_object('project_id', OLD.project_id, 'provider', OLD.provider, 'options',
null)::text);
ELSIF (OLD IS NULL) OR (OLD.options <> NEW.options) THEN
PERFORM pg_notify('integration', row_to_json(NEW)::text);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION notify_alert() RETURNS trigger AS
$$
DECLARE
clone jsonb;
BEGIN
clone = to_jsonb(NEW);
clone = jsonb_set(clone, '{created_at}', to_jsonb(CAST(EXTRACT(epoch FROM NEW.created_at) * 1000 AS BIGINT)));
IF NEW.deleted_at NOTNULL THEN
clone = jsonb_set(clone, '{deleted_at}', to_jsonb(CAST(EXTRACT(epoch FROM NEW.deleted_at) * 1000 AS BIGINT)));
END IF;
PERFORM pg_notify('alert', clone::text);
RETURN NEW;
END ;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION notify_project() RETURNS trigger AS
$$
BEGIN
PERFORM pg_notify('project', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO
$$
BEGIN
IF (with to_check (name) as (values ('alerts'),
('announcements'),
('assigned_sessions'),
('autocomplete'),
('basic_authentication'),
('dashboards'),
('dashboard_widgets'),
('errors'),
('integrations'),
('issues'),
('jira_cloud'),
('jobs'),
('metric_series'),
('metrics'),
('notifications'),
('oauth_authentication'),
('projects'),
('roles'),
('roles_projects'),
('searches'),
('sessions'),
('tenants'),
('traces'),
('user_favorite_errors'),
('user_favorite_sessions'),
('user_viewed_errors'),
('user_viewed_sessions'),
('users'),
('webhooks'),
('sessions_notes'),
('assist_records'),
('projects_stats'))
select bool_and(exists(select *
from information_schema.tables t
where table_schema = 'public'
AND table_name = to_check.name)) as all_present
from to_check) THEN
raise notice 'All public schema tables exists';
ELSE
raise notice 'Some or all public schema tables are missing, creating missing tables';
CREATE TABLE IF NOT EXISTS tenants
(
tenant_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_key text NOT NULL DEFAULT generate_api_key(20),
name text NOT NULL,
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
deleted_at timestamp without time zone NULL DEFAULT NULL,
license text NULL,
opt_out bool NOT NULL DEFAULT FALSE,
t_projects integer NOT NULL DEFAULT 1,
t_sessions bigint NOT NULL DEFAULT 0,
t_users integer NOT NULL DEFAULT 1,
t_integrations integer NOT NULL DEFAULT 0,
last_telemetry bigint NOT NULL DEFAULT CAST(EXTRACT(epoch FROM date_trunc('day', now())) * 1000 AS BIGINT)
);
CREATE TABLE IF NOT EXISTS roles
(
role_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
name text NOT NULL,
description text DEFAULT NULL,
permissions text[] NOT NULL DEFAULT '{}',
protected bool NOT NULL DEFAULT FALSE,
all_projects bool NOT NULL DEFAULT TRUE,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
deleted_at timestamp NULL DEFAULT NULL
);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'user_role') THEN
CREATE TYPE user_role AS ENUM ('owner','admin','member');
END IF;
CREATE TABLE IF NOT EXISTS users
(
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
email text NOT NULL UNIQUE,
role user_role NOT NULL DEFAULT 'member',
name text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
deleted_at timestamp without time zone NULL DEFAULT NULL,
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
jwt_iat timestamp without time zone NULL DEFAULT NULL,
data jsonb NOT NULL DEFAULT'{}'::jsonb,
weekly_report boolean NOT NULL DEFAULT TRUE,
origin text NULL DEFAULT NULL,
role_id integer REFERENCES roles (role_id) ON DELETE SET NULL,
internal_id text NULL DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS users_tenant_id_deleted_at_N_idx ON users (tenant_id) WHERE deleted_at ISNULL;
CREATE INDEX IF NOT EXISTS users_name_gin_idx ON users USING GIN (name gin_trgm_ops);
CREATE TABLE IF NOT EXISTS basic_authentication
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
password text DEFAULT NULL,
invitation_token text NULL DEFAULT NULL,
invited_at timestamp without time zone NULL DEFAULT NULL,
change_pwd_token text NULL DEFAULT NULL,
change_pwd_expire_at timestamp without time zone NULL DEFAULT NULL,
changed_at timestamp,
UNIQUE (user_id)
);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'oauth_provider') THEN
CREATE TYPE oauth_provider AS ENUM ('jira','github');
END IF;
CREATE TABLE IF NOT EXISTS oauth_authentication
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
provider oauth_provider NOT NULL,
provider_user_id text NOT NULL,
token text NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS oauth_authentication_unique_user_id_provider_idx ON oauth_authentication (user_id, provider);
CREATE TABLE IF NOT EXISTS projects
(
project_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_key varchar(20) NOT NULL UNIQUE DEFAULT generate_api_key(20),
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
name text NOT NULL,
active boolean NOT NULL,
sample_rate smallint NOT NULL DEFAULT 100 CHECK (sample_rate >= 0 AND sample_rate <= 100),
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
deleted_at timestamp without time zone NULL DEFAULT NULL,
max_session_duration integer NOT NULL DEFAULT 7200000,
metadata_1 text DEFAULT NULL,
metadata_2 text DEFAULT NULL,
metadata_3 text DEFAULT NULL,
metadata_4 text DEFAULT NULL,
metadata_5 text DEFAULT NULL,
metadata_6 text DEFAULT NULL,
metadata_7 text DEFAULT NULL,
metadata_8 text DEFAULT NULL,
metadata_9 text DEFAULT NULL,
metadata_10 text DEFAULT NULL,
save_request_payloads boolean NOT NULL DEFAULT FALSE,
gdpr jsonb NOT NULL DEFAULT'{
"maskEmails": true,
"sampleRate": 33,
"maskNumbers": false,
"defaultInputMode": "obscured"
}'::jsonb,
first_recorded_session_at timestamp without time zone NULL DEFAULT NULL,
sessions_last_check_at timestamp without time zone NULL DEFAULT NULL,
beacon_size integer NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS projects_tenant_id_idx ON public.projects (tenant_id);
CREATE INDEX IF NOT EXISTS projects_project_key_idx ON public.projects (project_key);
CREATE INDEX IF NOT EXISTS projects_project_id_deleted_at_n_idx ON public.projects (project_id) WHERE deleted_at IS NULL;
DROP TRIGGER IF EXISTS on_insert_or_update ON projects;
CREATE TRIGGER on_insert_or_update
AFTER INSERT OR UPDATE
ON projects
FOR EACH ROW
EXECUTE PROCEDURE notify_project();
CREATE TABLE IF NOT EXISTS roles_projects
(
role_id integer NOT NULL REFERENCES roles (role_id) ON DELETE CASCADE,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
CONSTRAINT roles_projects_pkey PRIMARY KEY (role_id, project_id)
);
CREATE INDEX IF NOT EXISTS roles_projects_role_id_idx ON roles_projects (role_id);
CREATE INDEX IF NOT EXISTS roles_projects_project_id_idx ON roles_projects (project_id);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'webhook_type') THEN
CREATE TYPE webhook_type AS ENUM ('webhook','slack','email','msteams');
END IF;
CREATE TABLE IF NOT EXISTS webhooks
(
webhook_id integer generated by DEFAULT as identity
constraint webhooks_pkey
primary key,
tenant_id integer NOT NULL
constraint webhooks_tenant_id_fkey
references tenants
on delete cascade,
endpoint text NOT NULL,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
deleted_at timestamp,
auth_header text,
type webhook_type NOT NULL DEFAULT 'webhook',
index integer DEFAULT 0 NOT NULL,
name varchar(100)
);
CREATE TABLE IF NOT EXISTS notifications
(
notification_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id integer REFERENCES tenants (tenant_id) ON DELETE CASCADE,
user_id integer REFERENCES users (user_id) ON DELETE CASCADE,
title text NOT NULL,
description text NOT NULL,
button_text varchar(80) NULL,
button_url text NULL,
image_url text NULL,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
options jsonb NOT NULL DEFAULT'{}'::jsonb,
CONSTRAINT notification_tenant_xor_user CHECK ( tenant_id NOTNULL AND user_id ISNULL OR
tenant_id ISNULL AND user_id NOTNULL )
);
CREATE INDEX IF NOT EXISTS notifications_user_id_index ON notifications (user_id);
CREATE INDEX IF NOT EXISTS notifications_tenant_id_index ON notifications (tenant_id);
CREATE INDEX IF NOT EXISTS notifications_created_at_index ON notifications (created_at DESC);
CREATE INDEX IF NOT EXISTS notifications_created_at_epoch_idx ON notifications (CAST(EXTRACT(EPOCH FROM created_at) * 1000 AS BIGINT) DESC);
CREATE TABLE IF NOT EXISTS user_viewed_notifications
(
user_id integer NOT NULL REFERENCES users (user_id) on delete cascade,
notification_id integer NOT NULL REFERENCES notifications (notification_id) on delete cascade,
constraint user_viewed_notifications_pkey primary key (user_id, notification_id)
);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'announcement_type') THEN
CREATE TYPE announcement_type AS ENUM ('notification','alert');
END IF;
CREATE TABLE IF NOT EXISTS announcements
(
announcement_id serial NOT NULL
constraint announcements_pk
primary key,
title text NOT NULL,
description text NOT NULL,
button_text varchar(30),
button_url text,
image_url text,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
type announcement_type DEFAULT 'notification'::announcement_type NOT NULL
);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'integration_provider') THEN
CREATE TYPE integration_provider AS ENUM ('bugsnag','cloudwatch','datadog','newrelic','rollbar','sentry','stackdriver','sumologic','elasticsearch'); --,'jira','github');
END IF;
CREATE TABLE IF NOT EXISTS integrations
(
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
provider integration_provider NOT NULL,
options jsonb NOT NULL,
request_data jsonb NOT NULL DEFAULT'{}'::jsonb,
PRIMARY KEY (project_id, provider)
);
DROP TRIGGER IF EXISTS on_insert_or_update_or_delete ON integrations;
CREATE TRIGGER on_insert_or_update_or_delete
AFTER INSERT OR UPDATE OR DELETE
ON integrations
FOR EACH ROW
EXECUTE PROCEDURE notify_integration();
CREATE TABLE IF NOT EXISTS jira_cloud
(
user_id integer NOT NULL
constraint jira_cloud_pk
primary key
constraint jira_cloud_users_fkey
references users
on delete cascade,
username text NOT NULL,
token text NOT NULL,
url text
);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'issue_type') THEN
CREATE TYPE issue_type AS ENUM (
'click_rage',
'dead_click',
'excessive_scrolling',
'bad_request',
'missing_resource',
'memory',
'cpu',
'slow_resource',
'slow_page_load',
'crash',
'ml_cpu',
'ml_memory',
'ml_dead_click',
'ml_click_rage',
'ml_mouse_thrashing',
'ml_excessive_scrolling',
'ml_slow_resources',
'custom',
'js_exception',
'mouse_thrashing',
'app_crash'
);
END IF;
CREATE TABLE IF NOT EXISTS issues
(
issue_id text NOT NULL PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
type issue_type NOT NULL,
context_string text NOT NULL,
context jsonb DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS issues_issue_id_type_idx ON issues (issue_id, type);
CREATE INDEX IF NOT EXISTS issues_project_id_issue_id_idx ON public.issues (project_id, issue_id);
CREATE INDEX IF NOT EXISTS issues_project_id_idx ON issues (project_id);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'error_source') THEN
CREATE TYPE error_source AS ENUM ('js_exception','bugsnag','cloudwatch','datadog','newrelic','rollbar','sentry','stackdriver','sumologic', 'elasticsearch');
END IF;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'error_status') THEN
CREATE TYPE error_status AS ENUM ('unresolved','resolved','ignored');
END IF;
CREATE TABLE IF NOT EXISTS errors
(
error_id text NOT NULL PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
source error_source NOT NULL,
name text DEFAULT NULL,
message text NOT NULL,
payload jsonb NOT NULL,
status error_status NOT NULL DEFAULT 'unresolved',
parent_error_id text DEFAULT NULL REFERENCES errors (error_id) ON DELETE SET NULL,
stacktrace jsonb, --to save the stacktrace and not query S3 another time
stacktrace_parsed_at timestamp
);
CREATE INDEX IF NOT EXISTS errors_project_id_source_idx ON errors (project_id, source);
CREATE INDEX IF NOT EXISTS errors_message_gin_idx ON public.errors USING GIN (message gin_trgm_ops);
CREATE INDEX IF NOT EXISTS errors_name_gin_idx ON public.errors USING GIN (name gin_trgm_ops);
CREATE INDEX IF NOT EXISTS errors_project_id_idx ON public.errors (project_id);
CREATE INDEX IF NOT EXISTS errors_project_id_status_idx ON public.errors (project_id, status);
CREATE INDEX IF NOT EXISTS errors_project_id_error_id_js_exception_idx ON public.errors (project_id, error_id) WHERE source = 'js_exception';
CREATE INDEX IF NOT EXISTS errors_project_id_error_id_idx ON public.errors (project_id, error_id);
CREATE INDEX IF NOT EXISTS errors_project_id_error_id_integration_idx ON public.errors (project_id, error_id) WHERE source != 'js_exception';
CREATE INDEX IF NOT EXISTS errors_error_id_idx ON errors (error_id);
CREATE INDEX IF NOT EXISTS errors_parent_error_id_idx ON errors (parent_error_id);
CREATE TABLE IF NOT EXISTS user_favorite_errors
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, error_id)
);
CREATE TABLE IF NOT EXISTS user_viewed_errors
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, error_id)
);
CREATE INDEX IF NOT EXISTS user_viewed_errors_user_id_idx ON public.user_viewed_errors (user_id);
CREATE INDEX IF NOT EXISTS user_viewed_errors_error_id_idx ON public.user_viewed_errors (error_id);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'platform') THEN
CREATE TYPE platform AS ENUM ('web','ios','android');
END IF;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'device_type') THEN
CREATE TYPE device_type AS ENUM ('desktop','tablet','mobile','other');
END IF;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'country') THEN
CREATE TYPE country AS ENUM ('UN','RW','SO','YE','IQ','SA','IR','CY','TZ','SY','AM','KE','CD','DJ','UG','CF','SC','JO','LB','KW','OM','QA','BH','AE','IL','TR','ET','ER','EG','SD','GR','BI','EE','LV','AZ','LT','SJ','GE','MD','BY','FI','AX','UA','MK','HU','BG','AL','PL','RO','XK','ZW','ZM','KM','MW','LS','BW','MU','SZ','RE','ZA','YT','MZ','MG','AF','PK','BD','TM','TJ','LK','BT','IN','MV','IO','NP','MM','UZ','KZ','KG','TF','HM','CC','PW','VN','TH','ID','LA','TW','PH','MY','CN','HK','BN','MO','KH','KR','JP','KP','SG','CK','TL','RU','MN','AU','CX','MH','FM','PG','SB','TV','NR','VU','NC','NF','NZ','FJ','LY','CM','SN','CG','PT','LR','CI','GH','GQ','NG','BF','TG','GW','MR','BJ','GA','SL','ST','GI','GM','GN','TD','NE','ML','EH','TN','ES','MA','MT','DZ','FO','DK','IS','GB','CH','SE','NL','AT','BE','DE','LU','IE','MC','FR','AD','LI','JE','IM','GG','SK','CZ','NO','VA','SM','IT','SI','ME','HR','BA','AO','NA','SH','BV','BB','CV','GY','GF','SR','PM','GL','PY','UY','BR','FK','GS','JM','DO','CU','MQ','BS','BM','AI','TT','KN','DM','AG','LC','TC','AW','VG','VC','MS','MF','BL','GP','GD','KY','BZ','SV','GT','HN','NI','CR','VE','EC','CO','PA','HT','AR','CL','BO','PE','MX','PF','PN','KI','TK','TO','WF','WS','NU','MP','GU','PR','VI','UM','AS','CA','US','PS','RS','AQ','SX','CW','BQ','SS','AC','AN','BU','CP','CS','CT','DD','DG','DY','EA','FQ','FX','HV','IC','JT','MI','NH','NQ','NT','PC','PU','PZ','RH','SU','TA','TP','VD','WK','YD','YU','ZR');
END IF;
CREATE TABLE IF NOT EXISTS sessions
(
session_id bigint PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
tracker_version text NOT NULL,
start_ts bigint NOT NULL,
duration integer NULL,
rev_id text DEFAULT NULL,
platform platform NOT NULL DEFAULT 'web',
is_snippet boolean NOT NULL DEFAULT FALSE,
user_id text DEFAULT NULL,
user_anonymous_id text DEFAULT NULL,
user_uuid uuid NOT NULL,
user_agent text DEFAULT NULL,
user_os text NOT NULL,
user_os_version text DEFAULT NULL,
user_browser text DEFAULT NULL,
user_browser_version text DEFAULT NULL,
user_device text NOT NULL,
user_device_type device_type NOT NULL,
user_device_memory_size integer DEFAULT NULL,
user_device_heap_size bigint DEFAULT NULL,
user_country country NOT NULL,
user_city text NULL,
user_state text NULL,
pages_count integer NOT NULL DEFAULT 0,
events_count integer NOT NULL DEFAULT 0,
errors_count integer NOT NULL DEFAULT 0,
watchdogs_score bigint NOT NULL DEFAULT 0,
issue_score bigint NOT NULL DEFAULT 0,
issue_types issue_type[] NOT NULL DEFAULT '{}'::issue_type[],
utm_source text DEFAULT NULL,
utm_medium text DEFAULT NULL,
utm_campaign text DEFAULT NULL,
referrer text DEFAULT NULL,
base_referrer text DEFAULT NULL,
file_key bytea DEFAULT NULL,
metadata_1 text DEFAULT NULL,
metadata_2 text DEFAULT NULL,
metadata_3 text DEFAULT NULL,
metadata_4 text DEFAULT NULL,
metadata_5 text DEFAULT NULL,
metadata_6 text DEFAULT NULL,
metadata_7 text DEFAULT NULL,
metadata_8 text DEFAULT NULL,
metadata_9 text DEFAULT NULL,
metadata_10 text DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS sessions_project_id_start_ts_idx ON sessions (project_id, start_ts);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_id_idx ON sessions (project_id, user_id);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_anonymous_id_idx ON sessions (project_id, user_anonymous_id);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_device_idx ON sessions (project_id, user_device);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_country_idx ON sessions (project_id, user_country);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_city_idx ON sessions (project_id, user_city);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_state_idx ON sessions (project_id, user_state);
CREATE INDEX IF NOT EXISTS sessions_project_id_user_browser_idx ON sessions (project_id, user_browser);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_1_idx ON sessions (project_id, metadata_1);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_2_idx ON sessions (project_id, metadata_2);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_3_idx ON sessions (project_id, metadata_3);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_4_idx ON sessions (project_id, metadata_4);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_5_idx ON sessions (project_id, metadata_5);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_6_idx ON sessions (project_id, metadata_6);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_7_idx ON sessions (project_id, metadata_7);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_8_idx ON sessions (project_id, metadata_8);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_9_idx ON sessions (project_id, metadata_9);
CREATE INDEX IF NOT EXISTS sessions_project_id_metadata_10_idx ON sessions (project_id, metadata_10);
CREATE INDEX IF NOT EXISTS sessions_project_id_watchdogs_score_idx ON sessions (project_id, watchdogs_score DESC);
CREATE INDEX IF NOT EXISTS sessions_platform_idx ON public.sessions (platform);
CREATE INDEX IF NOT EXISTS sessions_metadata1_gin_idx ON public.sessions USING GIN (metadata_1 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata2_gin_idx ON public.sessions USING GIN (metadata_2 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata3_gin_idx ON public.sessions USING GIN (metadata_3 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata4_gin_idx ON public.sessions USING GIN (metadata_4 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata5_gin_idx ON public.sessions USING GIN (metadata_5 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata6_gin_idx ON public.sessions USING GIN (metadata_6 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata7_gin_idx ON public.sessions USING GIN (metadata_7 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata8_gin_idx ON public.sessions USING GIN (metadata_8 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata9_gin_idx ON public.sessions USING GIN (metadata_9 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_metadata10_gin_idx ON public.sessions USING GIN (metadata_10 gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_user_device_gin_idx ON public.sessions USING GIN (user_device gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_user_id_gin_idx ON public.sessions USING GIN (user_id gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_user_anonymous_id_gin_idx ON public.sessions USING GIN (user_anonymous_id gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_start_ts_idx ON public.sessions (start_ts) WHERE duration > 0;
CREATE INDEX IF NOT EXISTS sessions_project_id_idx ON public.sessions (project_id) WHERE duration > 0;
CREATE INDEX IF NOT EXISTS sessions_session_id_project_id_start_ts_idx ON sessions (session_id, project_id, start_ts) WHERE duration > 0;
CREATE INDEX IF NOT EXISTS sessions_session_id_project_id_start_ts_durationNN_idx ON sessions (session_id, project_id, start_ts) WHERE duration IS NOT NULL;
CREATE INDEX IF NOT EXISTS sessions_user_id_useridNN_idx ON sessions (user_id) WHERE user_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS sessions_uid_projectid_startts_sessionid_uidNN_durGTZ_idx ON sessions (user_id, project_id, start_ts, session_id) WHERE user_id IS NOT NULL AND duration > 0;
CREATE INDEX IF NOT EXISTS sessions_utm_source_gin_idx ON public.sessions USING GIN (utm_source gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_utm_medium_gin_idx ON public.sessions USING GIN (utm_medium gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_utm_campaign_gin_idx ON public.sessions USING GIN (utm_campaign gin_trgm_ops);
CREATE INDEX IF NOT EXISTS sessions_base_referrer_gin_idx ON public.sessions USING GIN (base_referrer gin_trgm_ops);
BEGIN
ALTER TABLE public.sessions
ADD CONSTRAINT web_browser_constraint CHECK (
(sessions.platform = 'web' AND sessions.user_browser NOTNULL) OR
(sessions.platform != 'web' AND sessions.user_browser ISNULL));
EXCEPTION
WHEN duplicate_object THEN RAISE NOTICE 'Table constraint exists';
END;
BEGIN
ALTER TABLE public.sessions
ADD CONSTRAINT web_user_browser_version_constraint CHECK (
sessions.platform = 'web' OR sessions.user_browser_version ISNULL);
EXCEPTION
WHEN duplicate_object THEN RAISE NOTICE 'Table constraint exists';
END;
BEGIN
ALTER TABLE public.sessions
ADD CONSTRAINT web_user_agent_constraint CHECK (
(sessions.platform = 'web' AND sessions.user_agent NOTNULL) OR
(sessions.platform != 'web' AND sessions.user_agent ISNULL));
EXCEPTION
WHEN duplicate_object THEN RAISE NOTICE 'Table constraint already exists';
END;
CREATE TABLE IF NOT EXISTS user_viewed_sessions
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, session_id)
);
CREATE TABLE IF NOT EXISTS user_favorite_sessions
(
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, session_id)
);
CREATE INDEX IF NOT EXISTS user_favorite_sessions_user_id_session_id_idx ON user_favorite_sessions (user_id, session_id);
CREATE TABLE IF NOT EXISTS frontend_signals
(
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
timestamp bigint NOT NULL,
action text NOT NULL,
source text NOT NULL,
category text NOT NULL,
data jsonb,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL
);
CREATE INDEX IF NOT EXISTS frontend_signals_user_id_idx ON frontend_signals (user_id);
CREATE TABLE IF NOT EXISTS assigned_sessions
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
issue_id text NOT NULL,
provider oauth_provider NOT NULL,
created_by integer NOT NULL,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
provider_data jsonb DEFAULT'{}'::jsonb NOT NULL
);
CREATE INDEX IF NOT EXISTS assigned_sessions_session_id_idx ON assigned_sessions (session_id);
CREATE TABLE IF NOT EXISTS autocomplete
(
value text NOT NULL,
type text NOT NULL,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX IF NOT EXISTS autocomplete_unique_project_id_md5value_type_idx ON autocomplete (project_id, md5(value), type);
CREATE INDEX IF NOT EXISTS autocomplete_project_id_idx ON autocomplete (project_id);
CREATE INDEX IF NOT EXISTS autocomplete_type_idx ON public.autocomplete (type);
CREATE INDEX IF NOT EXISTS autocomplete_value_clickonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CLICK';
CREATE INDEX IF NOT EXISTS autocomplete_value_customonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CUSTOM';
CREATE INDEX IF NOT EXISTS autocomplete_value_graphqlonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'GRAPHQL';
CREATE INDEX IF NOT EXISTS autocomplete_value_inputonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'INPUT';
CREATE INDEX IF NOT EXISTS autocomplete_value_locationonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'LOCATION';
CREATE INDEX IF NOT EXISTS autocomplete_value_referreronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REFERRER';
CREATE INDEX IF NOT EXISTS autocomplete_value_requestonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REQUEST';
CREATE INDEX IF NOT EXISTS autocomplete_value_revidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REVID';
CREATE INDEX IF NOT EXISTS autocomplete_value_stateactiononly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'STATEACTION';
CREATE INDEX IF NOT EXISTS autocomplete_value_useranonymousidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERANONYMOUSID';
CREATE INDEX IF NOT EXISTS autocomplete_value_userbrowseronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERBROWSER';
CREATE INDEX IF NOT EXISTS autocomplete_value_usercountryonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERCOUNTRY';
CREATE INDEX IF NOT EXISTS autocomplete_value_userdeviceonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERDEVICE';
CREATE INDEX IF NOT EXISTS autocomplete_value_useridonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERID';
CREATE INDEX IF NOT EXISTS autocomplete_value_userosonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USEROS';
BEGIN
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'job_status') THEN
CREATE TYPE job_status AS ENUM ('scheduled','running','cancelled','failed','completed');
END IF;
END;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'job_action') THEN
CREATE TYPE job_action AS ENUM ('delete_user_data');
END IF;
CREATE TABLE IF NOT EXISTS jobs
(
job_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
description text NOT NULL,
status job_status NOT NULL,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
action job_action NOT NULL,
reference_id text NOT NULL,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at timestamp DEFAULT timezone('utc'::text, now()) NULL,
start_at timestamp NOT NULL,
errors text NULL
);
CREATE INDEX IF NOT EXISTS jobs_status_idx ON jobs (status);
CREATE INDEX IF NOT EXISTS jobs_start_at_idx ON jobs (start_at);
CREATE INDEX IF NOT EXISTS jobs_project_id_idx ON jobs (project_id);
CREATE TABLE IF NOT EXISTS traces
(
user_id integer NULL REFERENCES users (user_id) ON DELETE CASCADE,
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
created_at bigint NOT NULL DEFAULT (EXTRACT(EPOCH FROM now() at time zone 'utc') * 1000)::bigint,
auth text NULL,
action text NOT NULL,
method text NOT NULL,
path_format text NOT NULL,
endpoint text NOT NULL,
payload jsonb NULL,
parameters jsonb NULL,
status int NULL
);
CREATE INDEX IF NOT EXISTS traces_user_id_idx ON traces (user_id);
CREATE INDEX IF NOT EXISTS traces_tenant_id_idx ON traces (tenant_id);
CREATE INDEX IF NOT EXISTS traces_created_at_idx ON traces (created_at);
CREATE INDEX IF NOT EXISTS traces_action_idx ON traces (action);
CREATE TABLE IF NOT EXISTS metrics
(
metric_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer REFERENCES users (user_id) ON DELETE SET NULL,
name text NOT NULL,
is_public boolean NOT NULL DEFAULT TRUE,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
deleted_at timestamp,
edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
metric_type text NOT NULL DEFAULT 'timeseries',
view_type text NOT NULL DEFAULT 'lineChart',
metric_of text NOT NULL DEFAULT 'sessionCount',
metric_value text[] NOT NULL DEFAULT '{}'::text[],
metric_format text,
thumbnail text,
is_pinned boolean NOT NULL DEFAULT FALSE,
default_config jsonb NOT NULL DEFAULT '{
"col": 2,
"row": 2,
"position": 0
}'::jsonb,
data jsonb NULL
);
CREATE INDEX IF NOT EXISTS metrics_user_id_is_public_idx ON public.metrics (user_id, is_public);
CREATE TABLE IF NOT EXISTS metric_series
(
series_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
metric_id integer REFERENCES metrics (metric_id) ON DELETE CASCADE,
index integer NOT NULL,
name text NULL,
filter jsonb NOT NULL,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
deleted_at timestamp
);
CREATE INDEX IF NOT EXISTS metric_series_metric_id_idx ON public.metric_series (metric_id);
CREATE TABLE dashboards
(
dashboard_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer REFERENCES users (user_id) ON DELETE SET NULL,
name text NOT NULL,
description text NOT NULL DEFAULT '',
is_public boolean NOT NULL DEFAULT TRUE,
is_pinned boolean NOT NULL DEFAULT FALSE,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
deleted_at timestamp NULL DEFAULT NULL
);
CREATE TABLE dashboard_widgets
(
widget_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
dashboard_id integer NOT NULL REFERENCES dashboards (dashboard_id) ON DELETE CASCADE,
metric_id integer NOT NULL REFERENCES metrics (metric_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
config jsonb NOT NULL DEFAULT '{}'::jsonb
);
CREATE TABLE IF NOT EXISTS searches
(
search_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE,
name text NOT NULL,
filter jsonb NOT NULL,
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
deleted_at timestamp,
is_public boolean NOT NULL DEFAULT False
);
CREATE INDEX IF NOT EXISTS searches_user_id_is_public_idx ON public.searches (user_id, is_public);
CREATE INDEX IF NOT EXISTS searches_project_id_idx ON public.searches (project_id);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'alert_detection_method') THEN
CREATE TYPE alert_detection_method AS ENUM ('threshold', 'change');
END IF;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'alert_change_type') THEN
CREATE TYPE alert_change_type AS ENUM ('percent', 'change');
END IF;
CREATE TABLE IF NOT EXISTS alerts
(
alert_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
series_id integer NULL REFERENCES metric_series (series_id) ON DELETE CASCADE,
name text NOT NULL,
description text NULL DEFAULT NULL,
active boolean NOT NULL DEFAULT TRUE,
detection_method alert_detection_method NOT NULL,
change alert_change_type NOT NULL DEFAULT 'change',
query jsonb NOT NULL,
deleted_at timestamp NULL DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
options jsonb NOT NULL DEFAULT'{
"renotifyInterval": 1440
}'::jsonb
);
CREATE INDEX IF NOT EXISTS alerts_project_id_idx ON alerts (project_id);
CREATE INDEX IF NOT EXISTS alerts_series_id_idx ON alerts (series_id);
DROP TRIGGER IF EXISTS on_insert_or_update_or_delete ON alerts;
CREATE TRIGGER on_insert_or_update_or_delete
AFTER INSERT OR UPDATE OR DELETE
ON alerts
FOR EACH ROW
EXECUTE PROCEDURE notify_alert();
CREATE TABLE IF NOT EXISTS sessions_notes
(
note_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
message text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
user_id integer NULL REFERENCES users (user_id) ON DELETE SET NULL,
deleted_at timestamp without time zone NULL DEFAULT NULL,
tag text NULL,
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
timestamp integer NOT NULL DEFAULT -1,
is_public boolean NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS public.assist_records
(
record_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL,
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE SET NULL,
created_at bigint NOT NULL DEFAULT (EXTRACT(EPOCH FROM now() at time zone 'utc') * 1000)::bigint,
deleted_at timestamp without time zone NULL DEFAULT NULL,
name text NOT NULL,
file_key text NOT NULL,
duration integer NOT NULL
);
CREATE TABLE IF NOT EXISTS public.projects_stats
(
project_id integer NOT NULL,
created_at timestamp default (now() AT TIME ZONE 'utc'::text),
sessions_count integer NOT NULL DEFAULT 0,
events_count bigint NOT NULL DEFAULT 0,
last_update_at timestamp default (now() AT TIME ZONE 'utc'::text),
primary key (project_id, created_at)
);
CREATE INDEX IF NOT EXISTS projects_stats_project_id_idx ON public.projects_stats (project_id);
CREATE TABLE IF NOT EXISTS public.feature_flags
(
feature_flag_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
name text NOT NULL,
flag_key text NOT NULL,
description text NOT NULL,
flag_type text NOT NULL,
is_persist boolean NOT NULL DEFAULT FALSE,
is_active boolean NOT NULL DEFAULT FALSE,
created_by integer REFERENCES users (user_id) ON DELETE SET NULL,
updated_by integer REFERENCES users (user_id) ON DELETE SET NULL,
created_at timestamp without time zone NOT NULL DEFAULT timezone('utc'::text, now()),
updated_at timestamp without time zone NOT NULL DEFAULT timezone('utc'::text, now()),
deleted_at timestamp without time zone NULL DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_feature_flags_project_id ON public.feature_flags (project_id);
CREATE TABLE IF NOT EXISTS public.feature_flags_conditions
(
condition_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
feature_flag_id integer NOT NULL REFERENCES feature_flags (feature_flag_id) ON DELETE CASCADE,
name text NOT NULL,
rollout_percentage integer NOT NULL,
filters jsonb NOT NULL DEFAULT '[]'::jsonb
);
RAISE NOTICE 'Created missing public schema tables';
END IF;
END;
$$
LANGUAGE plpgsql;
DO
$$
BEGIN
IF (with to_check (name) as (values ('clicks'),
('errors'),
('graphql'),
('inputs'),
('pages'),
('performance'),
('resources'),
('state_actions'))
select bool_and(exists(select *
from information_schema.tables t
where table_schema = 'events'
AND table_name = to_check.name)) as all_present
from to_check) THEN
raise notice 'All events schema tables exists';
ELSE
CREATE TABLE IF NOT EXISTS events.pages
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
host text NOT NULL,
path text NOT NULL,
query text NULL,
referrer text DEFAULT NULL,
base_referrer text DEFAULT NULL,
dom_building_time integer DEFAULT NULL,
dom_content_loaded_time integer DEFAULT NULL,
load_time integer DEFAULT NULL,
first_paint_time integer DEFAULT NULL,
first_contentful_paint_time integer DEFAULT NULL,
speed_index integer DEFAULT NULL,
visually_complete integer DEFAULT NULL,
time_to_interactive integer DEFAULT NULL,
response_time bigint DEFAULT NULL,
response_end bigint DEFAULT NULL,
ttfb integer DEFAULT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS pages_session_id_idx ON events.pages (session_id);
CREATE INDEX IF NOT EXISTS pages_base_referrer_gin_idx ON events.pages USING GIN (base_referrer gin_trgm_ops);
CREATE INDEX IF NOT EXISTS pages_timestamp_idx ON events.pages (timestamp);
CREATE INDEX IF NOT EXISTS pages_session_id_timestamp_idx ON events.pages (session_id, timestamp);
CREATE INDEX IF NOT EXISTS pages_base_referrer_idx ON events.pages (base_referrer);
CREATE INDEX IF NOT EXISTS pages_response_time_idx ON events.pages (response_time);
CREATE INDEX IF NOT EXISTS pages_response_end_idx ON events.pages (response_end);
CREATE INDEX IF NOT EXISTS pages_path_gin_idx ON events.pages USING GIN (path gin_trgm_ops);
CREATE INDEX IF NOT EXISTS pages_path_idx ON events.pages (path);
CREATE INDEX IF NOT EXISTS pages_visually_complete_idx ON events.pages (visually_complete) WHERE visually_complete > 0;
CREATE INDEX IF NOT EXISTS pages_dom_building_time_idx ON events.pages (dom_building_time) WHERE dom_building_time > 0;
CREATE INDEX IF NOT EXISTS pages_load_time_idx ON events.pages (load_time) WHERE load_time > 0;
CREATE INDEX IF NOT EXISTS pages_first_contentful_paint_time_idx ON events.pages (first_contentful_paint_time) WHERE first_contentful_paint_time > 0;
CREATE INDEX IF NOT EXISTS pages_dom_content_loaded_time_idx ON events.pages (dom_content_loaded_time) WHERE dom_content_loaded_time > 0;
CREATE INDEX IF NOT EXISTS pages_first_paint_time_idx ON events.pages (first_paint_time) WHERE first_paint_time > 0;
CREATE INDEX IF NOT EXISTS pages_ttfb_idx ON events.pages (ttfb) WHERE ttfb > 0;
CREATE INDEX IF NOT EXISTS pages_time_to_interactive_idx ON events.pages (time_to_interactive) WHERE time_to_interactive > 0;
CREATE INDEX IF NOT EXISTS pages_session_id_timestamp_loadgt0NN_idx ON events.pages (session_id, timestamp) WHERE load_time > 0 AND load_time IS NOT NULL;
CREATE INDEX IF NOT EXISTS pages_session_id_timestamp_visualgt0nn_idx ON events.pages (session_id, timestamp) WHERE visually_complete > 0 AND visually_complete IS NOT NULL;
CREATE INDEX IF NOT EXISTS pages_timestamp_metgt0_idx ON events.pages (timestamp) WHERE response_time > 0 OR
first_paint_time >
0 OR
dom_content_loaded_time >
0 OR
ttfb > 0 OR
time_to_interactive >
0;
CREATE INDEX IF NOT EXISTS pages_session_id_speed_indexgt0nn_idx ON events.pages (session_id, speed_index) WHERE speed_index > 0 AND speed_index IS NOT NULL;
CREATE INDEX IF NOT EXISTS pages_session_id_timestamp_dom_building_timegt0nn_idx ON events.pages (session_id, timestamp, dom_building_time) WHERE dom_building_time > 0 AND dom_building_time IS NOT NULL;
CREATE INDEX IF NOT EXISTS pages_path_session_id_timestamp_idx ON events.pages (path, session_id, timestamp);
CREATE INDEX IF NOT EXISTS pages_path_pathLNGT2_idx ON events.pages (path) WHERE length(path) > 2;
CREATE INDEX IF NOT EXISTS pages_query_nn_idx ON events.pages (query) WHERE query IS NOT NULL;
CREATE INDEX IF NOT EXISTS pages_query_nn_gin_idx ON events.pages USING GIN (query gin_trgm_ops) WHERE query IS NOT NULL;
CREATE TABLE IF NOT EXISTS events.clicks
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
label text DEFAULT NULL,
url text DEFAULT '' NOT NULL,
path text,
selector text DEFAULT '' NOT NULL,
hesitation integer DEFAULT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS clicks_session_id_idx ON events.clicks (session_id);
CREATE INDEX IF NOT EXISTS clicks_label_idx ON events.clicks (label);
CREATE INDEX IF NOT EXISTS clicks_label_gin_idx ON events.clicks USING GIN (label gin_trgm_ops);
CREATE INDEX IF NOT EXISTS clicks_timestamp_idx ON events.clicks (timestamp);
CREATE INDEX IF NOT EXISTS clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp);
CREATE INDEX IF NOT EXISTS clicks_url_idx ON events.clicks (url);
CREATE INDEX IF NOT EXISTS clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector);
CREATE INDEX IF NOT EXISTS clicks_session_id_timestamp_idx ON events.clicks (session_id, timestamp);
CREATE INDEX IF NOT EXISTS clicks_selector_idx ON events.clicks (selector);
CREATE INDEX IF NOT EXISTS clicks_path_idx ON events.clicks (path);
CREATE INDEX IF NOT EXISTS clicks_path_gin_idx ON events.clicks USING GIN (path gin_trgm_ops);
CREATE TABLE IF NOT EXISTS events.inputs
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
label text DEFAULT NULL,
value text DEFAULT NULL,
duration integer DEFAULT NULL,
hesitation integer DEFAULT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS inputs_session_id_idx ON events.inputs (session_id);
CREATE INDEX IF NOT EXISTS inputs_label_gin_idx ON events.inputs USING GIN (label gin_trgm_ops);
CREATE INDEX IF NOT EXISTS inputs_timestamp_idx ON events.inputs (timestamp);
CREATE INDEX IF NOT EXISTS inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp);
CREATE TABLE IF NOT EXISTS events.errors
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS errors_session_id_idx ON events.errors (session_id);
CREATE INDEX IF NOT EXISTS errors_timestamp_idx ON events.errors (timestamp);
CREATE INDEX IF NOT EXISTS errors_session_id_timestamp_error_id_idx ON events.errors (session_id, timestamp, error_id);
CREATE INDEX IF NOT EXISTS errors_error_id_timestamp_idx ON events.errors (error_id, timestamp);
CREATE INDEX IF NOT EXISTS errors_timestamp_error_id_session_id_idx ON events.errors (timestamp, error_id, session_id);
CREATE INDEX IF NOT EXISTS errors_error_id_timestamp_session_id_idx ON events.errors (error_id, timestamp, session_id);
CREATE INDEX IF NOT EXISTS errors_error_id_idx ON events.errors (error_id);
CREATE TABLE IF NOT EXISTS errors_tags
(
key text NOT NULL,
value text NOT NULL,
created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'),
error_id text NOT NULL REFERENCES errors (error_id) ON DELETE CASCADE,
session_id bigint NOT NULL,
message_id bigint NOT NULL,
FOREIGN KEY (session_id, message_id) REFERENCES events.errors (session_id, message_id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS errors_tags_error_id_idx ON errors_tags (error_id);
CREATE INDEX IF NOT EXISTS errors_tags_session_id_idx ON errors_tags (session_id);
CREATE INDEX IF NOT EXISTS errors_tags_message_id_idx ON errors_tags (message_id);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'http_method') THEN
CREATE TYPE http_method AS ENUM ('GET','HEAD','POST','PUT','DELETE','CONNECT','OPTIONS','TRACE','PATCH');
END IF;
CREATE TABLE IF NOT EXISTS events.graphql
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
name text NOT NULL,
request_body text NULL,
response_body text NULL,
method http_method NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS graphql_name_idx ON events.graphql (name);
CREATE INDEX IF NOT EXISTS graphql_name_gin_idx ON events.graphql USING GIN (name gin_trgm_ops);
CREATE INDEX IF NOT EXISTS graphql_timestamp_idx ON events.graphql (timestamp);
CREATE INDEX IF NOT EXISTS graphql_request_body_nn_idx ON events.graphql (request_body) WHERE request_body IS NOT NULL;
CREATE INDEX IF NOT EXISTS graphql_request_body_nn_gin_idx ON events.graphql USING GIN (request_body gin_trgm_ops) WHERE request_body IS NOT NULL;
CREATE INDEX IF NOT EXISTS graphql_response_body_nn_idx ON events.graphql (response_body) WHERE response_body IS NOT NULL;
CREATE INDEX IF NOT EXISTS graphql_response_body_nn_gin_idx ON events.graphql USING GIN (response_body gin_trgm_ops) WHERE response_body IS NOT NULL;
CREATE TABLE IF NOT EXISTS events.state_actions
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
name text NOT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS state_actions_name_gin_idx ON events.state_actions USING GIN (name gin_trgm_ops);
CREATE INDEX IF NOT EXISTS state_actions_timestamp_idx ON events.state_actions (timestamp);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'resource_type') THEN
CREATE TYPE events.resource_type AS ENUM ('other', 'script', 'stylesheet', 'fetch', 'img', 'media');
END IF;
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'resource_method') THEN
CREATE TYPE events.resource_method AS ENUM ('GET' , 'HEAD' , 'POST' , 'PUT' , 'DELETE' , 'CONNECT' , 'OPTIONS' , 'TRACE' , 'PATCH' );
END IF;
CREATE TABLE IF NOT EXISTS events.resources
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
message_id bigint NOT NULL,
timestamp bigint NOT NULL,
duration bigint NULL,
type events.resource_type NOT NULL,
url text NOT NULL,
url_host text NOT NULL,
url_hostpath text NOT NULL,
success boolean NOT NULL,
status smallint NULL,
method events.resource_method NULL,
ttfb bigint NULL,
header_size bigint NULL,
encoded_body_size integer NULL,
decoded_body_size integer NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id, timestamp)
);
CREATE INDEX IF NOT EXISTS resources_session_id_idx ON events.resources (session_id);
CREATE INDEX IF NOT EXISTS resources_status_idx ON events.resources (status);
CREATE INDEX IF NOT EXISTS resources_type_idx ON events.resources (type);
CREATE INDEX IF NOT EXISTS resources_url_host_idx ON events.resources (url_host);
CREATE INDEX IF NOT EXISTS resources_timestamp_idx ON events.resources (timestamp);
CREATE INDEX IF NOT EXISTS resources_success_idx ON events.resources (success);
CREATE INDEX IF NOT EXISTS resources_url_hostpath_gin_idx ON events.resources USING GIN (url_hostpath gin_trgm_ops);
CREATE INDEX IF NOT EXISTS resources_timestamp_type_durationgt0NN_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL;
CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_type_idx ON events.resources (session_id, timestamp, type);
CREATE INDEX IF NOT EXISTS resources_timestamp_type_durationgt0NN_noFetch_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL AND type != 'fetch';
CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_url_host_fail_idx ON events.resources (session_id, timestamp, url_host) WHERE success = FALSE;
CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_url_host_firstparty_idx ON events.resources (session_id, timestamp, url_host) WHERE type IN ('fetch', 'script');
CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_duration_durationgt0NN_img_idx ON events.resources (session_id, timestamp, duration) WHERE duration > 0 AND duration IS NOT NULL AND type = 'img';
CREATE INDEX IF NOT EXISTS resources_timestamp_session_id_idx ON events.resources (timestamp, session_id);
CREATE INDEX IF NOT EXISTS resources_timestamp_duration_durationgt0NN_idx ON events.resources (timestamp, duration) WHERE duration > 0 AND duration IS NOT NULL;
CREATE TABLE IF NOT EXISTS events.performance
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
timestamp bigint NOT NULL,
message_id bigint NOT NULL,
host text NULL DEFAULT NULL,
path text NULL DEFAULT NULL,
query text NULL DEFAULT NULL,
min_fps smallint NOT NULL,
avg_fps smallint NOT NULL,
max_fps smallint NOT NULL,
min_cpu smallint NOT NULL,
avg_cpu smallint NOT NULL,
max_cpu smallint NOT NULL,
min_total_js_heap_size bigint NOT NULL,
avg_total_js_heap_size bigint NOT NULL,
max_total_js_heap_size bigint NOT NULL,
min_used_js_heap_size bigint NOT NULL,
avg_used_js_heap_size bigint NOT NULL,
max_used_js_heap_size bigint NOT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, message_id)
);
CREATE INDEX IF NOT EXISTS performance_session_id_idx ON events.performance (session_id);
CREATE INDEX IF NOT EXISTS performance_timestamp_idx ON events.performance (timestamp);
CREATE INDEX IF NOT EXISTS performance_session_id_timestamp_idx ON events.performance (session_id, timestamp);
CREATE INDEX IF NOT EXISTS performance_avg_cpu_gt0_idx ON events.performance (avg_cpu) WHERE avg_cpu > 0;
CREATE INDEX IF NOT EXISTS performance_avg_used_js_heap_size_gt0_idx ON events.performance (avg_used_js_heap_size) WHERE avg_used_js_heap_size > 0;
END IF;
END;
$$
LANGUAGE plpgsql;
DO
$$
BEGIN
IF (with to_check (name) as (values ('customs'),
('issues'),
('requests'))
select bool_and(exists(select *
from information_schema.tables t
where table_schema = 'events_common'
AND table_name = to_check.name)) as all_present
from to_check) THEN
raise notice 'All events_common schema tables exists';
ELSE
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'custom_level') THEN
CREATE TYPE events_common.custom_level AS ENUM ('info','error');
END IF;
CREATE TABLE IF NOT EXISTS events_common.customs
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
timestamp bigint NOT NULL,
seq_index integer NOT NULL,
name text NOT NULL,
payload jsonb NOT NULL,
level events_common.custom_level NOT NULL DEFAULT 'info',
tab_id text NULL,
PRIMARY KEY (session_id, timestamp, seq_index)
);
CREATE INDEX IF NOT EXISTS customs_name_idx ON events_common.customs (name);
CREATE INDEX IF NOT EXISTS customs_name_gin_idx ON events_common.customs USING GIN (name gin_trgm_ops);
CREATE INDEX IF NOT EXISTS customs_timestamp_idx ON events_common.customs (timestamp);
CREATE TABLE IF NOT EXISTS events_common.issues
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
timestamp bigint NOT NULL,
seq_index integer NOT NULL,
issue_id text NOT NULL REFERENCES issues (issue_id) ON DELETE CASCADE,
payload jsonb DEFAULT NULL,
tab_id text NULL,
PRIMARY KEY (session_id, timestamp, seq_index)
);
CREATE INDEX IF NOT EXISTS issues_issue_id_timestamp_idx ON events_common.issues (issue_id, timestamp);
CREATE INDEX IF NOT EXISTS issues_timestamp_idx ON events_common.issues (timestamp);
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'http_method') THEN
CREATE TYPE http_method AS ENUM ('GET','HEAD','POST','PUT','DELETE','CONNECT','OPTIONS','TRACE','PATCH');
END IF;
CREATE TABLE IF NOT EXISTS events_common.requests
(
session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE,
timestamp bigint NOT NULL,
seq_index integer NOT NULL,
url text NOT NULL,
duration integer NOT NULL,
success boolean NOT NULL,
request_body text NULL,
response_body text NULL,
status_code smallint NULL,
method http_method NULL,
host text NULL,
path text NULL,
query text NULL,
tab_id text NULL,
PRIMARY KEY (session_id, timestamp, seq_index)
);
CREATE INDEX IF NOT EXISTS requests_duration_idx ON events_common.requests (duration);
CREATE INDEX IF NOT EXISTS requests_timestamp_idx ON events_common.requests (timestamp);
CREATE INDEX IF NOT EXISTS requests_timestamp_session_id_failed_idx ON events_common.requests (timestamp, session_id) WHERE success = FALSE;
CREATE INDEX IF NOT EXISTS requests_request_body_nn_gin_idx ON events_common.requests USING GIN (request_body gin_trgm_ops) WHERE request_body IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_response_body_nn_gin_idx ON events_common.requests USING GIN (response_body gin_trgm_ops) WHERE response_body IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_status_code_nn_idx ON events_common.requests (status_code) WHERE status_code IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_session_id_status_code_nn_idx ON events_common.requests (session_id, status_code) WHERE status_code IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_host_nn_gin_idx ON events_common.requests USING GIN (host gin_trgm_ops) WHERE host IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_path_nn_idx ON events_common.requests (path) WHERE path IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_path_nn_gin_idx ON events_common.requests USING GIN (path gin_trgm_ops) WHERE path IS NOT NULL;
CREATE INDEX IF NOT EXISTS requests_query_nn_gin_idx ON events_common.requests USING GIN (query gin_trgm_ops) WHERE query IS NOT NULL;
END IF;
END;
$$
LANGUAGE plpgsql;
COMMIT;