Merge remote-tracking branch 'origin/api-ms-teams' into api-v1.9.5

# Conflicts:
#	api/routers/core.py
This commit is contained in:
Taha Yassine Kraiem 2022-11-30 15:47:48 +01:00
commit 7bb6c25f17
24 changed files with 544 additions and 275 deletions

View file

@ -1,9 +1,14 @@
import json
import logging
import time
from datetime import datetime
from decouple import config
import schemas
from chalicelib.core import notifications, slack, webhook
from chalicelib.core import notifications, webhook
from chalicelib.core.collaboration_msteams import MSTeams
from chalicelib.core.collaboration_slack import Slack
from chalicelib.utils import pg_client, helper, email_helper
from chalicelib.utils.TimeUTC import TimeUTC
@ -95,7 +100,7 @@ def process_notifications(data):
for c in n["options"].pop("message"):
if c["type"] not in full:
full[c["type"]] = []
if c["type"] in ["slack", "email"]:
if c["type"] in ["slack", "msteams", "email"]:
full[c["type"]].append({
"notification": n,
"destination": c["value"]
@ -107,13 +112,21 @@ def process_notifications(data):
for t in full.keys():
for i in range(0, len(full[t]), BATCH_SIZE):
notifications_list = full[t][i:i + BATCH_SIZE]
if notifications_list is None or len(notifications_list) == 0:
break
if t == "slack":
try:
slack.send_batch(notifications_list=notifications_list)
send_to_slack_batch(notifications_list=notifications_list)
except Exception as e:
logging.error("!!!Error while sending slack notifications batch")
logging.error(str(e))
elif t == "msteams":
try:
send_to_msteams_batch(notifications_list=notifications_list)
except Exception as e:
logging.error("!!!Error while sending msteams notifications batch")
logging.error(str(e))
elif t == "email":
try:
send_by_email_batch(notifications_list=notifications_list)
@ -149,16 +162,60 @@ def send_by_email_batch(notifications_list):
time.sleep(1)
def send_to_slack_batch(notifications_list):
webhookId_map = {}
for n in notifications_list:
if n.get("destination") not in webhookId_map:
webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []}
webhookId_map[n.get("destination")]["batch"].append({"text": n["notification"]["description"] \
+ f"\n<{config('SITE_URL')}{n['notification']['buttonUrl']}|{n['notification']['buttonText']}>",
"title": n["notification"]["title"],
"title_link": n["notification"]["buttonUrl"],
"ts": datetime.now().timestamp()})
for batch in webhookId_map.keys():
Slack.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch,
attachments=webhookId_map[batch]["batch"])
def send_to_msteams_batch(notifications_list):
webhookId_map = {}
for n in notifications_list:
if n.get("destination") not in webhookId_map:
webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []}
link = f"[{n['notification']['buttonText']}]({config('SITE_URL')}{n['notification']['buttonUrl']})"
webhookId_map[n.get("destination")]["batch"].append({"type": "ColumnSet",
"style": "emphasis",
"separator": True,
"bleed": True,
"columns": [{
"width": "stretch",
"items": [
{"type": "TextBlock",
"text": n["notification"]["title"],
"style": "heading",
"size": "Large"},
{"type": "TextBlock",
"spacing": "small",
"text": n["notification"]["description"],
"wrap": True},
{"type": "TextBlock",
"spacing": "small",
"text": link}
]
}]})
for batch in webhookId_map.keys():
MSTeams.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch,
attachments=webhookId_map[batch]["batch"])
def delete(project_id, alert_id):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
UPDATE public.alerts
SET
deleted_at = timezone('utc'::text, now()),
active = FALSE
WHERE
alert_id = %(alert_id)s AND project_id=%(project_id)s;""",
cur.mogrify(""" UPDATE public.alerts
SET deleted_at = timezone('utc'::text, now()),
active = FALSE
WHERE alert_id = %(alert_id)s AND project_id=%(project_id)s;""",
{"alert_id": alert_id, "project_id": project_id})
)
return {"data": {"state": "success"}}

View file

@ -0,0 +1,45 @@
from abc import ABC, abstractmethod
import schemas
class BaseCollaboration(ABC):
@classmethod
@abstractmethod
def add(cls, tenant_id, data: schemas.AddCollaborationSchema):
pass
@classmethod
@abstractmethod
def say_hello(cls, url):
pass
@classmethod
@abstractmethod
def send_raw(cls, tenant_id, webhook_id, body):
pass
@classmethod
@abstractmethod
def send_batch(cls, tenant_id, webhook_id, attachments):
pass
@classmethod
@abstractmethod
def __share(cls, tenant_id, integration_id, attachments):
pass
@classmethod
@abstractmethod
def share_session(cls, tenant_id, project_id, session_id, user, comment, integration_id=None):
pass
@classmethod
@abstractmethod
def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None):
pass
@classmethod
@abstractmethod
def __get(cls, tenant_id, integration_id=None):
pass

View file

@ -0,0 +1,190 @@
import json
import requests
from decouple import config
import schemas
from chalicelib.core import webhook
from chalicelib.core.collaboration_base import BaseCollaboration
class MSTeams(BaseCollaboration):
@classmethod
def add(cls, tenant_id, data: schemas.AddCollaborationSchema):
if cls.say_hello(data.url):
return webhook.add(tenant_id=tenant_id,
endpoint=data.url,
webhook_type="msteams",
name=data.name)
return None
# https://messagecardplayground.azurewebsites.net
# https://adaptivecards.io/designer/
@classmethod
def say_hello(cls, url):
r = requests.post(
url=url,
json={
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Hello message",
"title": "Welcome to OpenReplay"
})
if r.status_code != 200:
print("MSTeams integration failed")
print(r.text)
return False
return True
@classmethod
def send_raw(cls, tenant_id, webhook_id, body):
integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id)
if integration is None:
return {"errors": ["slack integration not found"]}
try:
r = requests.post(
url=integration["endpoint"],
json=body,
timeout=5)
if r.status_code != 200:
print(f"!! issue sending slack raw; webhookId:{webhook_id} code:{r.status_code}")
print(r.text)
return None
except requests.exceptions.Timeout:
print(f"!! Timeout sending slack raw webhookId:{webhook_id}")
return None
except Exception as e:
print(f"!! Issue sending slack raw webhookId:{webhook_id}")
print(str(e))
return None
return {"data": r.text}
@classmethod
def send_batch(cls, tenant_id, webhook_id, attachments):
integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id)
if integration is None:
return {"errors": ["msteams integration not found"]}
print(f"====> sending msteams batch notification: {len(attachments)}")
for i in range(0, len(attachments), 100):
print(json.dumps({"type": "message",
"attachments": [
{"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": None,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": attachments[i:i + 100]}}
]}))
r = requests.post(
url=integration["endpoint"],
json={"type": "message",
"attachments": [
{"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": None,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": attachments[i:i + 100]}}
]})
if r.status_code != 200:
print("!!!! something went wrong")
print(r)
print(r.text)
@classmethod
def __share(cls, tenant_id, integration_id, attachement):
integration = cls.__get(tenant_id=tenant_id, integration_id=integration_id)
if integration is None:
return {"errors": ["Microsoft Teams integration not found"]}
r = requests.post(
url=integration["endpoint"],
json={"type": "message",
"attachments": [
{"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": None,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [attachement]}}
]
})
return r.text
@classmethod
def share_session(cls, tenant_id, project_id, session_id, user, comment, integration_id=None):
title = f"[{user}](mailto:{user}) has shared the below session!"
link = f"{config('SITE_URL')}/{project_id}/session/{session_id}"
link = f"[{link}]({link})"
args = {"type": "ColumnSet",
"style": "emphasis",
"separator": True,
"bleed": True,
"columns": [{
"width": "stretch",
"items": [
{"type": "TextBlock",
"text": title,
"style": "heading",
"size": "Large"},
{"type": "TextBlock",
"spacing": "small",
"text": link}
]
}]}
if comment and len(comment) > 0:
args["columns"][0]["items"].append({
"type": "TextBlock",
"spacing": "small",
"text": comment
})
data = cls.__share(tenant_id, integration_id, attachement=args)
if "errors" in data:
return data
return {"data": data}
@classmethod
def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None):
title = f"[{user}](mailto:{user}) has shared the below error!"
link = f"{config('SITE_URL')}/{project_id}/errors/{error_id}"
link = f"[{link}]({link})"
args = {"type": "ColumnSet",
"style": "emphasis",
"separator": True,
"bleed": True,
"columns": [{
"width": "stretch",
"items": [
{"type": "TextBlock",
"text": title,
"style": "heading",
"size": "Large"},
{"type": "TextBlock",
"spacing": "small",
"text": link}
]
}]}
if comment and len(comment) > 0:
args["columns"][0]["items"].append({
"type": "TextBlock",
"spacing": "small",
"text": comment
})
data = cls.__share(tenant_id, integration_id, attachement=args)
if "errors" in data:
return data
return {"data": data}
@classmethod
def __get(cls, tenant_id, integration_id=None):
if integration_id is not None:
return webhook.get_webhook(tenant_id=tenant_id, webhook_id=integration_id,
webhook_type=schemas.WebhookType.msteams)
integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type=schemas.WebhookType.msteams)
if integrations is None or len(integrations) == 0:
return None
return integrations[0]

View file

@ -1,19 +1,20 @@
import requests
from decouple import config
from datetime import datetime
import schemas
from chalicelib.core import webhook
from chalicelib.core.collaboration_base import BaseCollaboration
class Slack:
class Slack(BaseCollaboration):
@classmethod
def add_channel(cls, tenant_id, **args):
url = args["url"]
name = args["name"]
if cls.say_hello(url):
def add(cls, tenant_id, data: schemas.AddCollaborationSchema):
if cls.say_hello(data.url):
return webhook.add(tenant_id=tenant_id,
endpoint=url,
endpoint=data.url,
webhook_type="slack",
name=name)
name=data.name)
return None
@classmethod
@ -34,37 +35,6 @@ class Slack:
return False
return True
@classmethod
def send_text_attachments(cls, tenant_id, webhook_id, text, **args):
integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id)
if integration is None:
return {"errors": ["slack integration not found"]}
try:
r = requests.post(
url=integration["endpoint"],
json={
"attachments": [
{
"text": text,
"ts": datetime.now().timestamp(),
**args
}
]
},
timeout=5)
if r.status_code != 200:
print(f"!! issue sending slack text attachments; webhookId:{webhook_id} code:{r.status_code}")
print(r.text)
return None
except requests.exceptions.Timeout:
print(f"!! Timeout sending slack text attachments webhookId:{webhook_id}")
return None
except Exception as e:
print(f"!! Issue sending slack text attachments webhookId:{webhook_id}")
print(str(e))
return None
return {"data": r.text}
@classmethod
def send_raw(cls, tenant_id, webhook_id, body):
integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id)
@ -105,24 +75,12 @@ class Slack:
print(r.text)
@classmethod
def __share_to_slack(cls, tenant_id, integration_id, fallback, pretext, title, title_link, text):
def __share(cls, tenant_id, integration_id, attachement):
integration = cls.__get(tenant_id=tenant_id, integration_id=integration_id)
if integration is None:
return {"errors": ["slack integration not found"]}
r = requests.post(
url=integration["endpoint"],
json={
"attachments": [
{
"fallback": fallback,
"pretext": pretext,
"title": title,
"title_link": title_link,
"text": text,
"ts": datetime.now().timestamp()
}
]
})
attachement["ts"] = datetime.now().timestamp()
r = requests.post(url=integration["endpoint"], json={"attachments": [attachement]})
return r.text
@classmethod
@ -132,7 +90,10 @@ class Slack:
"title": f"{config('SITE_URL')}/{project_id}/session/{session_id}",
"title_link": f"{config('SITE_URL')}/{project_id}/session/{session_id}",
"text": comment}
return {"data": cls.__share_to_slack(tenant_id, integration_id, **args)}
data = cls.__share(tenant_id, integration_id, attachement=args)
if "errors" in data:
return data
return {"data": data}
@classmethod
def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None):
@ -141,19 +102,18 @@ class Slack:
"title": f"{config('SITE_URL')}/{project_id}/errors/{error_id}",
"title_link": f"{config('SITE_URL')}/{project_id}/errors/{error_id}",
"text": comment}
return {"data": cls.__share_to_slack(tenant_id, integration_id, **args)}
@classmethod
def has_slack(cls, tenant_id):
integration = cls.__get(tenant_id=tenant_id)
return not (integration is None or len(integration) == 0)
data = cls.__share(tenant_id, integration_id, attachement=args)
if "errors" in data:
return data
return {"data": data}
@classmethod
def __get(cls, tenant_id, integration_id=None):
if integration_id is not None:
return webhook.get(tenant_id=tenant_id, webhook_id=integration_id)
return webhook.get_webhook(tenant_id=tenant_id, webhook_id=integration_id,
webhook_type=schemas.WebhookType.slack)
integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type="slack")
integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type=schemas.WebhookType.slack)
if integrations is None or len(integrations) == 0:
return None
return integrations[0]

View file

@ -127,7 +127,7 @@ def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=
{",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at" if include_last_session else ""}
{',s.gdpr' if include_gdpr else ''}
FROM public.projects AS s
where s.project_id =%(project_id)s
WHERE s.project_id =%(project_id)s
AND s.deleted_at IS NULL
LIMIT 1;""",
{"project_id": project_id})
@ -148,7 +148,7 @@ def get_project_by_key(tenant_id, project_key, include_last_session=False, inclu
{",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at" if include_last_session else ""}
{',s.gdpr' if include_gdpr else ''}
FROM public.projects AS s
where s.project_key =%(project_key)s
WHERE s.project_key =%(project_key)s
AND s.deleted_at IS NULL
LIMIT 1;""",
{"project_key": project_key})
@ -201,7 +201,7 @@ def count_by_tenant(tenant_id):
SELECT
count(s.project_id)
FROM public.projects AS s
where s.deleted_at IS NULL;""")
WHERE s.deleted_at IS NULL;""")
return cur.fetchone()["count"]
@ -212,7 +212,7 @@ def get_gdpr(project_id):
SELECT
gdpr
FROM public.projects AS s
where s.project_id =%(project_id)s
WHERE s.project_id =%(project_id)s
AND s.deleted_at IS NULL;""",
{"project_id": project_id})
)
@ -241,7 +241,7 @@ def get_internal_project_id(project_key):
cur.mogrify("""\
SELECT project_id
FROM public.projects
where project_key =%(project_key)s AND deleted_at ISNULL;""",
WHERE project_key =%(project_key)s AND deleted_at ISNULL;""",
{"project_key": project_key})
)
row = cur.fetchone()
@ -254,7 +254,7 @@ def get_project_key(project_id):
cur.mogrify("""\
SELECT project_key
FROM public.projects
where project_id =%(project_id)s AND deleted_at ISNULL;""",
WHERE project_id =%(project_id)s AND deleted_at ISNULL;""",
{"project_id": project_id})
)
project = cur.fetchone()
@ -268,7 +268,7 @@ def get_capture_status(project_id):
SELECT
sample_rate AS rate, sample_rate=100 AS capture_all
FROM public.projects
where project_id =%(project_id)s AND deleted_at ISNULL;""",
WHERE project_id =%(project_id)s AND deleted_at ISNULL;""",
{"project_id": project_id})
)
return helper.dict_to_camel_case(cur.fetchone())

View file

@ -319,7 +319,7 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues,
transitions ::: if transited from the first stage to the last - 1
else - 0
errors ::: a dictionary where the keys are all unique issues (currently context-wise)
errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise)
the values are lists
if an issue happened between the first stage to the last - 1
else - 0

View file

@ -1,21 +0,0 @@
from datetime import datetime
from decouple import config
from chalicelib.core.collaboration_slack import Slack
def send_batch(notifications_list):
if notifications_list is None or len(notifications_list) == 0:
return
webhookId_map = {}
for n in notifications_list:
if n.get("destination") not in webhookId_map:
webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []}
webhookId_map[n.get("destination")]["batch"].append({"text": n["notification"]["description"] \
+ f"\n<{config('SITE_URL')}{n['notification']['buttonUrl']}|{n['notification']['buttonText']}>",
"title": n["notification"]["title"],
"title_link": n["notification"]["buttonUrl"],
"ts": datetime.now().timestamp()})
for batch in webhookId_map.keys():
Slack.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch,
attachments=webhookId_map[batch]["batch"])

View file

@ -12,7 +12,7 @@ def get_by_id(webhook_id):
cur.mogrify("""\
SELECT w.*
FROM public.webhooks AS w
where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""",
WHERE w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""",
{"webhook_id": webhook_id})
)
w = helper.dict_to_camel_case(cur.fetchone())
@ -21,15 +21,14 @@ def get_by_id(webhook_id):
return w
def get(tenant_id, webhook_id):
def get_webhook(tenant_id, webhook_id, webhook_type='webhook'):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
SELECT
webhook_id AS integration_id, webhook_id AS id, w.*
FROM public.webhooks AS w
where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""",
{"webhook_id": webhook_id})
cur.mogrify("""SELECT w.*
FROM public.webhooks AS w
WHERE w.webhook_id =%(webhook_id)s
AND deleted_at ISNULL AND type=%(webhook_type)s;""",
{"webhook_id": webhook_id, "webhook_type": webhook_type})
)
w = helper.dict_to_camel_case(cur.fetchone())
if w:
@ -40,11 +39,9 @@ def get(tenant_id, webhook_id):
def get_by_type(tenant_id, webhook_type):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
SELECT
w.webhook_id AS integration_id, w.webhook_id AS id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at
FROM public.webhooks AS w
WHERE w.type =%(type)s AND deleted_at ISNULL;""",
cur.mogrify("""SELECT w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at
FROM public.webhooks AS w
WHERE w.type =%(type)s AND deleted_at ISNULL;""",
{"type": webhook_type})
)
webhooks = helper.list_to_camel_case(cur.fetchall())
@ -55,22 +52,12 @@ def get_by_type(tenant_id, webhook_type):
def get_by_tenant(tenant_id, replace_none=False):
with pg_client.PostgresClient() as cur:
cur.execute("""\
SELECT
webhook_id AS integration_id, webhook_id AS id, w.*
FROM public.webhooks AS w
WHERE deleted_at ISNULL;"""
)
cur.execute("""SELECT w.*
FROM public.webhooks AS w
WHERE deleted_at ISNULL AND type='webhook';""")
all = helper.list_to_camel_case(cur.fetchall())
if replace_none:
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
for k in w.keys():
if w[k] is None:
w[k] = ''
else:
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
return all
@ -83,7 +70,7 @@ def update(tenant_id, webhook_id, changes, replace_none=False):
UPDATE public.webhooks
SET {','.join(sub_query)}
WHERE webhook_id =%(id)s AND deleted_at ISNULL
RETURNING webhook_id AS integration_id, webhook_id AS id,*;""",
RETURNING *;""",
{"id": webhook_id, **changes})
)
w = helper.dict_to_camel_case(cur.fetchone())
@ -100,7 +87,7 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="",
query = cur.mogrify("""\
INSERT INTO public.webhooks(endpoint,auth_header,type,name)
VALUES (%(endpoint)s, %(auth_header)s, %(type)s,%(name)s)
RETURNING webhook_id AS integration_id, webhook_id AS id,*;""",
RETURNING *;""",
{"endpoint": endpoint, "auth_header": auth_header,
"type": webhook_type, "name": name})
cur.execute(

View file

@ -12,6 +12,7 @@ from chalicelib.core import log_tool_rollbar, sourcemaps, events, sessions_assig
log_tool_newrelic, announcements, log_tool_bugsnag, weekly_report, integration_jira_cloud, integration_github, \
assist, mobile, signup, tenants, boarding, notifications, webhook, users, \
custom_metrics, saved_search, integrations_global
from chalicelib.core.collaboration_msteams import MSTeams
from chalicelib.core.collaboration_slack import Slack
from chalicelib.utils import helper, captcha
from or_dependencies import OR_context
@ -104,21 +105,27 @@ def get_integrations_status(projectId: int, context: schemas.CurrentContext = De
return {"data": data}
@app.post('/{projectId}/integrations/{integration}/notify/{integrationId}/{source}/{sourceId}', tags=["integrations"])
def integration_notify(projectId: int, integration: str, integrationId: int, source: str, sourceId: str,
@app.post('/{projectId}/integrations/{integration}/notify/{webhookId}/{source}/{sourceId}', tags=["integrations"])
def integration_notify(projectId: int, integration: str, webhookId: int, source: str, sourceId: str,
data: schemas.IntegrationNotificationSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
comment = None
if data.comment:
comment = data.comment
if integration == "slack":
args = {"tenant_id": context.tenant_id,
"user": context.email, "comment": comment, "project_id": projectId,
"integration_id": integrationId}
args = {"tenant_id": context.tenant_id,
"user": context.email, "comment": comment, "project_id": projectId,
"integration_id": webhookId}
if integration == schemas.WebhookType.slack:
if source == "sessions":
return Slack.share_session(session_id=sourceId, **args)
elif source == "errors":
return Slack.share_error(error_id=sourceId, **args)
elif integration == schemas.WebhookType.msteams:
if source == "sessions":
return MSTeams.share_session(session_id=sourceId, **args)
elif source == "errors":
return MSTeams.share_error(error_id=sourceId, **args)
return {"data": None}
@ -872,17 +879,18 @@ def get_boarding_state_integrations(context: schemas.CurrentContext = Depends(OR
@app.get('/integrations/slack/channels', tags=["integrations"])
def get_slack_channels(context: schemas.CurrentContext = Depends(OR_context)):
return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type='slack')}
return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type=schemas.WebhookType.slack)}
@app.get('/integrations/slack/{integrationId}', tags=["integrations"])
def get_slack_webhook(integrationId: int, context: schemas.CurrentContext = Depends(OR_context)):
return {"data": webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId)}
@app.get('/integrations/slack/{webhookId}', tags=["integrations"])
def get_slack_webhook(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)):
return {"data": webhook.get_webhook(tenant_id=context.tenant_id, webhook_id=webhookId,
webhook_type=schemas.WebhookType.slack)}
@app.delete('/integrations/slack/{integrationId}', tags=["integrations"])
def delete_slack_integration(integrationId: int, context: schemas.CurrentContext = Depends(OR_context)):
return webhook.delete(context.tenant_id, integrationId)
@app.delete('/integrations/slack/{webhookId}', tags=["integrations"])
def delete_slack_integration(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)):
return webhook.delete(context.tenant_id, webhookId)
@app.put('/webhooks', tags=["webhooks"])
@ -965,6 +973,38 @@ def get_limits(context: schemas.CurrentContext = Depends(OR_context)):
}
}
@app.get('/integrations/msteams/channels', tags=["integrations"])
def get_msteams_channels(context: schemas.CurrentContext = Depends(OR_context)):
return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type=schemas.WebhookType.msteams)}
@app.post('/integrations/msteams', tags=['integrations'])
def add_msteams_integration(data: schemas.AddCollaborationSchema,
context: schemas.CurrentContext = Depends(OR_context)):
n = MSTeams.add(tenant_id=context.tenant_id, data=data)
if n is None:
return {
"errors": [
"We couldn't send you a test message on your Microsoft Teams channel. Please verify your webhook url."]
}
return {"data": n}
@app.post('/integrations/msteams/{webhookId}', tags=['integrations'])
def edit_msteams_integration(webhookId: int, data: schemas.EditCollaborationSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
if len(data.url) > 0:
old = webhook.get_webhook(tenant_id=context.tenant_id, webhook_id=webhookId,
webhook_type=schemas.WebhookType.msteams)
if old["endpoint"] != data.url:
if not MSTeams.say_hello(data.url):
return {
"errors": [
"We couldn't send you a test message on your Microsoft Teams channel. Please verify your webhook url."]
}
return {"data": webhook.update(tenant_id=context.tenant_id, webhook_id=webhookId,
changes={"name": data.name, "endpoint": data.url})}
@public_app.get('/general_stats', tags=["private"], include_in_schema=False)
def get_general_stats():

View file

@ -62,8 +62,8 @@ def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)):
@app.post('/integrations/slack', tags=['integrations'])
@app.put('/integrations/slack', tags=['integrations'])
def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentContext = Depends(OR_context)):
n = Slack.add_channel(tenant_id=context.tenant_id, url=data.url, name=data.name)
def add_slack_integration(data: schemas.AddCollaborationSchema, context: schemas.CurrentContext = Depends(OR_context)):
n = Slack.add(tenant_id=context.tenant_id, data=data)
if n is None:
return {
"errors": ["We couldn't send you a test message on your Slack channel. Please verify your webhook url."]
@ -72,10 +72,10 @@ def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentConte
@app.post('/integrations/slack/{integrationId}', tags=['integrations'])
def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = Body(...),
def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
if len(data.url) > 0:
old = webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId)
old = webhook.get_webhook(tenant_id=context.tenant_id, webhook_id=integrationId)
if old["endpoint"] != data.url:
if not Slack.say_hello(data.url):
return {

View file

@ -78,14 +78,13 @@ class CurrentContext(CurrentAPIContext):
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
class AddSlackSchema(BaseModel):
class AddCollaborationSchema(BaseModel):
name: str = Field(...)
url: HttpUrl = Field(...)
class EditSlackSchema(BaseModel):
class EditCollaborationSchema(AddCollaborationSchema):
name: Optional[str] = Field(None)
url: HttpUrl = Field(...)
class CreateNotificationSchema(BaseModel):
@ -1121,3 +1120,10 @@ class SessionUpdateNoteSchema(SessionNoteSchema):
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"

3
ee/api/.gitignore vendored
View file

@ -183,6 +183,8 @@ Pipfile
#exp /chalicelib/core/alerts_processor.py
/chalicelib/core/announcements.py
/chalicelib/core/autocomplete.py
/chalicelib/core/collaboration_base.py
/chalicelib/core/collaboration_msteams.py
/chalicelib/core/collaboration_slack.py
/chalicelib/core/countries.py
#exp /chalicelib/core/errors.py
@ -214,7 +216,6 @@ Pipfile
/chalicelib/core/sessions_metas.py
/chalicelib/core/sessions_mobs.py
#exp /chalicelib/core/significance.py
/chalicelib/core/slack.py
/chalicelib/core/socket_ios.py
/chalicelib/core/sourcemaps.py
/chalicelib/core/sourcemaps_parser.py

View file

@ -119,7 +119,7 @@ def get_role_by_name(tenant_id, name):
cur.execute(
cur.mogrify("""SELECT *
FROM public.roles
where tenant_id =%(tenant_id)s
WHERE tenant_id =%(tenant_id)s
AND deleted_at IS NULL
AND name ILIKE %(name)s;""",
{"tenant_id": tenant_id, "name": name})

View file

@ -326,7 +326,7 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues,
transitions ::: if transited from the first stage to the last - 1
else - 0
errors ::: a dictionary where the keys are all unique issues (currently context-wise)
errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise)
the values are lists
if an issue happened between the first stage to the last - 1
else - 0

View file

@ -304,7 +304,7 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues_
transitions ::: if transited from the first stage to the last - 1
else - 0
errors ::: a dictionary where the keys are all unique issues (currently context-wise)
errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise)
the values are lists
if an issue happened between the first stage to the last - 1
else - 0

View file

@ -12,7 +12,7 @@ def get_by_id(webhook_id):
cur.mogrify("""\
SELECT w.*
FROM public.webhooks AS w
where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""",
WHERE w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""",
{"webhook_id": webhook_id})
)
w = helper.dict_to_camel_case(cur.fetchone())
@ -21,15 +21,14 @@ def get_by_id(webhook_id):
return w
def get(tenant_id, webhook_id):
def get_webhook(tenant_id, webhook_id, webhook_type='webhook'):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
SELECT
webhook_id AS integration_id, webhook_id AS id, w.*
FROM public.webhooks AS w
where w.webhook_id =%(webhook_id)s AND w.tenant_id =%(tenant_id)s AND deleted_at ISNULL;""",
{"webhook_id": webhook_id, "tenant_id": tenant_id})
cur.mogrify("""SELECT w.*
FROM public.webhooks AS w
WHERE w.webhook_id =%(webhook_id)s AND w.tenant_id =%(tenant_id)s
AND deleted_at ISNULL AND type=%(webhook_type)s;""",
{"webhook_id": webhook_id, "webhook_type": webhook_type, "tenant_id": tenant_id})
)
w = helper.dict_to_camel_case(cur.fetchone())
if w:
@ -40,9 +39,7 @@ def get(tenant_id, webhook_id):
def get_by_type(tenant_id, webhook_type):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
SELECT
w.webhook_id AS integration_id, w.webhook_id AS id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at
cur.mogrify("""SELECT w.webhook_id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at
FROM public.webhooks AS w
WHERE w.tenant_id =%(tenant_id)s
AND w.type =%(type)s
@ -58,25 +55,16 @@ def get_by_type(tenant_id, webhook_type):
def get_by_tenant(tenant_id, replace_none=False):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify("""\
SELECT
webhook_id AS integration_id, webhook_id AS id,w.*
FROM public.webhooks AS w
where
w.tenant_id =%(tenant_id)s
AND deleted_at ISNULL;""",
cur.mogrify("""SELECT w.*
FROM public.webhooks AS w
WHERE w.tenant_id =%(tenant_id)s
AND deleted_at ISNULL
AND type='webhook';""",
{"tenant_id": tenant_id})
)
all = helper.list_to_camel_case(cur.fetchall())
if replace_none:
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
for k in w.keys():
if w[k] is None:
w[k] = ''
else:
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
for w in all:
w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"])
return all
@ -89,7 +77,7 @@ def update(tenant_id, webhook_id, changes, replace_none=False):
UPDATE public.webhooks
SET {','.join(sub_query)}
WHERE tenant_id =%(tenant_id)s AND webhook_id =%(id)s AND deleted_at ISNULL
RETURNING webhook_id AS integration_id, webhook_id AS id,*;""",
RETURNING *;""",
{"tenant_id": tenant_id, "id": webhook_id, **changes})
)
w = helper.dict_to_camel_case(cur.fetchone())
@ -106,7 +94,7 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="",
query = cur.mogrify("""\
INSERT INTO public.webhooks(tenant_id, endpoint,auth_header,type,name)
VALUES (%(tenant_id)s, %(endpoint)s, %(auth_header)s, %(type)s,%(name)s)
RETURNING webhook_id AS integration_id, webhook_id AS id,*;""",
RETURNING *;""",
{"tenant_id": tenant_id, "endpoint": endpoint, "auth_header": auth_header,
"type": webhook_type, "name": name})
cur.execute(

View file

@ -4,6 +4,8 @@ rm -rf ./chalicelib/core/alerts.py
#exp rm -rf ./chalicelib/core/alerts_processor.py
rm -rf ./chalicelib/core/announcements.py
rm -rf ./chalicelib/core/autocomplete.py
rm -rf ./chalicelib/core/collaboration_base.py
rm -rf ./chalicelib/core/collaboration_msteams.py
rm -rf ./chalicelib/core/collaboration_slack.py
rm -rf ./chalicelib/core/countries.py
#exp rm -rf ./chalicelib/core/errors.py
@ -36,7 +38,6 @@ rm -rf ./chalicelib/core/sessions_assignments.py
rm -rf ./chalicelib/core/sessions_metas.py
rm -rf ./chalicelib/core/sessions_mobs.py
#exp rm -rf ./chalicelib/core/significance.py
rm -rf ./chalicelib/core/slack.py
rm -rf ./chalicelib/core/socket_ios.py
rm -rf ./chalicelib/core/sourcemaps.py
rm -rf ./chalicelib/core/sourcemaps_parser.py

View file

@ -66,8 +66,8 @@ def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)):
@app.post('/integrations/slack', tags=['integrations'])
@app.put('/integrations/slack', tags=['integrations'])
def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentContext = Depends(OR_context)):
n = Slack.add_channel(tenant_id=context.tenant_id, url=data.url, name=data.name)
def add_slack_client(data: schemas.AddCollaborationSchema, context: schemas.CurrentContext = Depends(OR_context)):
n = Slack.add(tenant_id=context.tenant_id, data=data)
if n is None:
return {
"errors": ["We couldn't send you a test message on your Slack channel. Please verify your webhook url."]
@ -76,7 +76,7 @@ def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentConte
@app.post('/integrations/slack/{integrationId}', tags=['integrations'])
def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = Body(...),
def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
if len(data.url) > 0:
old = webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId)

View file

@ -0,0 +1,13 @@
BEGIN;
CREATE OR REPLACE FUNCTION openreplay_version()
RETURNS text AS
$$
SELECT 'v1.8.3-ee'
$$ LANGUAGE sql IMMUTABLE;
ALTER TABLE IF EXISTS public.webhooks
ALTER COLUMN type SET DEFAULT 'webhook';
ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams';
COMMIT;

View file

@ -18,4 +18,6 @@ CREATE TABLE IF NOT EXISTS assist_records
duration integer NOT NULL
);
ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams';
COMMIT;

View file

@ -147,7 +147,7 @@ $$
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,
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,
@ -187,9 +187,9 @@ $$
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'),
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,
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,
@ -284,25 +284,25 @@ $$
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'webhook_type') THEN
create type webhook_type as enum ('webhook','slack','email');
CREATE TYPE webhook_type AS ENUM ('webhook','slack','email','msteams');
END IF;
create table IF NOT EXISTS webhooks
CREATE TABLE IF NOT EXISTS webhooks
(
webhook_id integer generated by default as identity
webhook_id integer generated by DEFAULT as identity
constraint webhooks_pkey
primary key,
tenant_id integer not null
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,
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,
index integer default 0 not null,
type webhook_type NOT NULL DEFAULT 'webhook',
index integer DEFAULT 0 NOT NULL,
name varchar(100)
);
@ -340,9 +340,9 @@ $$
funnel_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,
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
);
@ -353,21 +353,21 @@ $$
IF NOT EXISTS(SELECT *
FROM pg_type typ
WHERE typ.typname = 'announcement_type') THEN
create type announcement_type as enum ('notification','alert');
CREATE TYPE announcement_type AS ENUM ('notification','alert');
END IF;
create table IF NOT EXISTS announcements
CREATE TABLE IF NOT EXISTS announcements
(
announcement_id serial not null
announcement_id serial NOT NULL
constraint announcements_pk
primary key,
title text not null,
description text not null,
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
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
type announcement_type DEFAULT 'notification'::announcement_type NOT NULL
);
IF NOT EXISTS(SELECT *
@ -396,14 +396,14 @@ $$
CREATE TABLE IF NOT EXISTS jira_cloud
(
user_id integer not null
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,
username text NOT NULL,
token text NOT NULL,
url text
);
@ -653,8 +653,8 @@ $$
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
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);
@ -707,8 +707,8 @@ $$
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,
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
);
@ -810,9 +810,9 @@ $$
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,
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
);
@ -864,7 +864,7 @@ $$
(
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'),
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,

View file

@ -0,0 +1,13 @@
BEGIN;
CREATE OR REPLACE FUNCTION openreplay_version()
RETURNS text AS
$$
SELECT 'v1.8.3'
$$ LANGUAGE sql IMMUTABLE;
ALTER TABLE IF EXISTS public.webhooks
ALTER COLUMN type SET DEFAULT 'webhook';
ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams';
COMMIT;

View file

@ -0,0 +1,10 @@
BEGIN;
CREATE OR REPLACE FUNCTION openreplay_version()
RETURNS text AS
$$
SELECT 'v1.9.5'
$$ LANGUAGE sql IMMUTABLE;
ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams';
COMMIT;

View file

@ -9,7 +9,6 @@ $$
SELECT 'v1.9.0'
$$ LANGUAGE sql IMMUTABLE;
-- --- accounts.sql ---
CREATE OR REPLACE FUNCTION generate_api_key(length integer) RETURNS text AS
$$
@ -29,7 +28,6 @@ begin
end;
$$ LANGUAGE plpgsql;
-- --- events.sql ---
CREATE OR REPLACE FUNCTION events.funnel(steps integer[], m integer) RETURNS boolean AS
$$
@ -54,7 +52,6 @@ BEGIN
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- --- integrations.sql ---
CREATE OR REPLACE FUNCTION notify_integration() RETURNS trigger AS
$$
@ -70,7 +67,6 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
-- --- alerts.sql ---
CREATE OR REPLACE FUNCTION notify_alert() RETURNS trigger AS
$$
@ -87,7 +83,6 @@ BEGIN
END ;
$$ LANGUAGE plpgsql;
-- --- projects.sql ---
CREATE OR REPLACE FUNCTION notify_project() RETURNS trigger AS
$$
@ -110,11 +105,9 @@ $$
ELSE
raise notice 'Creating DB';
-- --- public.sql ---
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- --- accounts.sql ---
CREATE TABLE tenants
(
@ -141,9 +134,9 @@ $$
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'),
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,
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
@ -171,7 +164,6 @@ $$
);
CREATE UNIQUE INDEX oauth_authentication_unique_user_id_provider_idx ON oauth_authentication (user_id, provider);
-- --- projects.sql ---
CREATE TABLE projects
(
@ -214,25 +206,22 @@ $$
EXECUTE PROCEDURE notify_project();
-- --- webhooks.sql ---
CREATE TYPE webhook_type AS ENUM ('webhook', 'slack', 'email', 'msteams');
create type webhook_type as enum ('webhook', 'slack', 'email');
create table webhooks
CREATE TABLE webhooks
(
webhook_id integer generated by default as identity
webhook_id integer generated by DEFAULT as identity
constraint webhooks_pkey
primary key,
endpoint text not null,
created_at timestamp default timezone('utc'::text, now()) not null,
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,
index integer default 0 not null,
type webhook_type NOT NULL DEFAULT 'webhook',
index integer DEFAULT 0 NOT NULL,
name varchar(100)
);
-- --- notifications.sql ---
CREATE TABLE notifications
(
@ -258,16 +247,15 @@ $$
constraint user_viewed_notifications_pkey primary key (user_id, notification_id)
);
-- --- funnels.sql ---
CREATE TABLE funnels
(
funnel_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,
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
);
@ -275,25 +263,23 @@ $$
CREATE INDEX funnels_user_id_is_public_idx ON public.funnels (user_id, is_public);
CREATE INDEX funnels_project_id_idx ON public.funnels (project_id);
-- --- announcements.sql ---
create type announcement_type as enum ('notification', 'alert');
CREATE TYPE announcement_type AS ENUM ('notification', 'alert');
create table announcements
CREATE TABLE announcements
(
announcement_id serial not null
announcement_id serial NOT NULL
constraint announcements_pk
primary key,
title text not null,
description text not null,
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
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
type announcement_type DEFAULT 'notification'::announcement_type NOT NULL
);
-- --- integrations.sql ---
CREATE TYPE integration_provider AS ENUM ('bugsnag', 'cloudwatch', 'datadog', 'newrelic', 'rollbar', 'sentry', 'stackdriver', 'sumologic', 'elasticsearch'); --, 'jira', 'github');
CREATE TABLE integrations
@ -312,20 +298,19 @@ $$
EXECUTE PROCEDURE notify_integration();
create table jira_cloud
CREATE TABLE jira_cloud
(
user_id integer not null
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,
username text NOT NULL,
token text NOT NULL,
url text
);
-- --- issues.sql ---
CREATE TYPE issue_type AS ENUM (
'click_rage',
@ -361,7 +346,6 @@ $$
CREATE INDEX issues_context_string_gin_idx ON public.issues USING GIN (context_string gin_trgm_ops);
CREATE INDEX issues_project_id_idx ON issues (project_id);
-- --- errors.sql ---
CREATE TYPE error_source AS ENUM ('js_exception', 'bugsnag', 'cloudwatch', 'datadog', 'newrelic', 'rollbar', 'sentry', 'stackdriver', 'sumologic', 'elasticsearch');
CREATE TYPE error_status AS ENUM ('unresolved', 'resolved', 'ignored');
@ -406,7 +390,6 @@ $$
CREATE INDEX user_viewed_errors_error_id_idx ON public.user_viewed_errors (error_id);
-- --- sessions.sql ---
CREATE TYPE device_type AS ENUM ('desktop', 'tablet', 'mobile', 'other');
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');
CREATE TYPE platform AS ENUM ('web','ios','android');
@ -532,21 +515,18 @@ $$
);
CREATE INDEX user_favorite_sessions_user_id_session_id_idx ON user_favorite_sessions (user_id, session_id);
-- --- assignments.sql ---
create table assigned_sessions
CREATE TABLE 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
created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL,
provider_data jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE INDEX assigned_sessions_session_id_idx ON assigned_sessions (session_id);
-- --- events_common.sql ---
CREATE TYPE events_common.custom_level AS ENUM ('info','error');
@ -611,7 +591,6 @@ $$
CREATE INDEX requests_query_nn_idx ON events_common.requests (query) WHERE query IS NOT NULL;
CREATE INDEX requests_query_nn_gin_idx ON events_common.requests USING GIN (query gin_trgm_ops) WHERE query IS NOT NULL;
-- --- events.sql ---
CREATE TABLE events.pages
(
@ -847,8 +826,6 @@ $$
CREATE INDEX performance_avg_used_js_heap_size_gt0_idx ON events.performance (avg_used_js_heap_size) WHERE avg_used_js_heap_size > 0;
-- --- autocomplete.sql ---
CREATE TABLE autocomplete
(
value text NOT NULL,
@ -887,8 +864,8 @@ $$
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,
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
);
@ -971,9 +948,9 @@ $$
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,
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
);
@ -1013,7 +990,7 @@ $$
(
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'),
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,