pulled main

This commit is contained in:
Андрей Бабушкин 2025-02-26 13:40:16 +01:00
commit 1299e14475
199 changed files with 1251 additions and 2809 deletions

View file

@ -55,8 +55,8 @@ def __get_sessions_list(project: schemas.ProjectContext, user_id, data: schemas.
return sessions_search.search_sessions(data=data.series[0].filter, project=project, user_id=user_id)
def __get_heat_map_chart(project: schemas.ProjectContext, user_id, data: schemas.CardHeatMap,
include_mobs: bool = True):
def get_heat_map_chart(project: schemas.ProjectContext, user_id, data: schemas.CardHeatMap,
include_mobs: bool = True):
if len(data.series) == 0:
return None
data.series[0].filter.filters += data.series[0].filter.events
@ -156,7 +156,7 @@ def get_chart(project: schemas.ProjectContext, data: schemas.CardSchema, user_id
supported = {
schemas.MetricType.TIMESERIES: __get_timeseries_chart,
schemas.MetricType.TABLE: __get_table_chart,
schemas.MetricType.HEAT_MAP: __get_heat_map_chart,
schemas.MetricType.HEAT_MAP: get_heat_map_chart,
schemas.MetricType.FUNNEL: __get_funnel_chart,
schemas.MetricType.PATH_ANALYSIS: __get_path_analysis_chart
}
@ -201,12 +201,12 @@ def get_issues(project: schemas.ProjectContext, user_id: int, data: schemas.Card
return supported.get(data.metric_type, not_supported)()
def __get_global_card_info(data: schemas.CardSchema):
def get_global_card_info(data: schemas.CardSchema):
r = {"hideExcess": data.hide_excess, "compareTo": data.compare_to, "rows": data.rows}
return r
def __get_path_analysis_card_info(data: schemas.CardPathAnalysis):
def get_path_analysis_card_info(data: schemas.CardPathAnalysis):
r = {"start_point": [s.model_dump() for s in data.start_point],
"start_type": data.start_type,
"excludes": [e.model_dump() for e in data.excludes],
@ -221,8 +221,8 @@ def create_card(project: schemas.ProjectContext, user_id, data: schemas.CardSche
if data.session_id is not None:
session_data = {"sessionId": data.session_id}
else:
session_data = __get_heat_map_chart(project=project, user_id=user_id,
data=data, include_mobs=False)
session_data = get_heat_map_chart(project=project, user_id=user_id,
data=data, include_mobs=False)
if session_data is not None:
session_data = {"sessionId": session_data["sessionId"]}
@ -235,9 +235,9 @@ def create_card(project: schemas.ProjectContext, user_id, data: schemas.CardSche
series_len = len(data.series)
params = {"user_id": user_id, "project_id": project.project_id, **data.model_dump(), **_data,
"default_config": json.dumps(data.default_config.model_dump()), "card_info": None}
params["card_info"] = __get_global_card_info(data=data)
params["card_info"] = get_global_card_info(data=data)
if data.metric_type == schemas.MetricType.PATH_ANALYSIS:
params["card_info"] = {**params["card_info"], **__get_path_analysis_card_info(data=data)}
params["card_info"] = {**params["card_info"], **get_path_analysis_card_info(data=data)}
params["card_info"] = json.dumps(params["card_info"])
query = """INSERT INTO metrics (project_id, user_id, name, is_public,
@ -299,9 +299,9 @@ def update_card(metric_id, user_id, project_id, data: schemas.CardSchema):
d_series_ids.append(i)
params["d_series_ids"] = tuple(d_series_ids)
params["session_data"] = json.dumps(metric["data"])
params["card_info"] = __get_global_card_info(data=data)
params["card_info"] = get_global_card_info(data=data)
if data.metric_type == schemas.MetricType.PATH_ANALYSIS:
params["card_info"] = {**params["card_info"], **__get_path_analysis_card_info(data=data)}
params["card_info"] = {**params["card_info"], **get_path_analysis_card_info(data=data)}
elif data.metric_type == schemas.MetricType.HEAT_MAP:
if data.session_id is not None:
params["session_data"] = json.dumps({"sessionId": data.session_id})

View file

@ -149,7 +149,7 @@ def get_selectors_by_url_and_session_id(project_id, session_id, data: schemas.Ge
query_from = f"{exp_ch_helper.get_main_events_table(0)} AS main_events"
with ch_client.ClickHouseClient() as cur:
query = cur.format(query=f"""SELECT main_events.selector AS selector,
query = cur.format(query=f"""SELECT CAST(`$properties`.selector AS String) AS selector,
COUNT(1) AS count
FROM {query_from}
WHERE {" AND ".join(constraints)}

View file

@ -175,7 +175,7 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas
value_key=e_k
) if not specific_condition else specific_condition)
full_args = {"eventTypes": tuple(event_types), **full_args, **values}
full_args = {"eventTypes": event_types, **full_args, **values}
n_stages = len(n_stages_query)
if n_stages == 0:
return []

View file

@ -381,7 +381,7 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu
filter_type = f.type
f.value = helper.values_for_operator(value=f.value, op=f.operator)
f_k = f"f_value{i}"
full_args = {**full_args, f_k: f.value, **sh.multi_values(f.value, value_key=f_k)}
full_args = {**full_args, f_k: sh.single_value(f.value), **sh.multi_values(f.value, value_key=f_k)}
op = sh.get_sql_operator(f.operator) \
if filter_type not in [schemas.FilterType.EVENTS_COUNT] else f.operator.value
is_any = sh.isAny_opreator(f.operator)

View file

@ -64,3 +64,12 @@ def isAny_opreator(op: schemas.SearchEventOperator):
def isUndefined_operator(op: schemas.SearchEventOperator):
return op in [schemas.SearchEventOperator.IS_UNDEFINED]
def single_value(values):
if values is not None and isinstance(values, list):
for i, v in enumerate(values):
if isinstance(v, Enum):
values[i] = v.value
return values

View file

@ -38,6 +38,9 @@ def force_is_event(events_enum: list[Type[Enum]]):
def fn(value: list):
if value is not None and isinstance(value, list):
for v in value:
if v.get("type") is None:
v["isEvent"] = False
continue
r = False
for en in events_enum:
if en.has_value(v["type"]) or en.has_value(v["type"].lower()):

View file

@ -10,5 +10,5 @@ func IsMobileType(id int) bool {
}
func IsDOMType(id int) bool {
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 43 == id || 52 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 68 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 117 == id || 118 == id || 119 == id || 122 == id || 93 == id || 96 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 106 == id || 111 == id
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 34 == id || 35 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 43 == id || 52 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 68 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 117 == id || 118 == id || 119 == id || 122 == id || 93 == id || 96 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 106 == id || 111 == id
}

View file

@ -35,6 +35,8 @@ const (
MsgPageEventDeprecated = 31
MsgInputEvent = 32
MsgPageEvent = 33
MsgStringDictGlobal = 34
MsgSetNodeAttributeDictGlobal = 35
MsgCSSInsertRule = 37
MsgCSSDeleteRule = 38
MsgFetch = 39
@ -1015,6 +1017,54 @@ func (msg *PageEvent) TypeID() int {
return 33
}
type StringDictGlobal struct {
message
Key uint64
Value string
}
func (msg *StringDictGlobal) Encode() []byte {
buf := make([]byte, 21+len(msg.Value))
buf[0] = 34
p := 1
p = WriteUint(msg.Key, buf, p)
p = WriteString(msg.Value, buf, p)
return buf[:p]
}
func (msg *StringDictGlobal) Decode() Message {
return msg
}
func (msg *StringDictGlobal) TypeID() int {
return 34
}
type SetNodeAttributeDictGlobal struct {
message
ID uint64
Name uint64
Value uint64
}
func (msg *SetNodeAttributeDictGlobal) Encode() []byte {
buf := make([]byte, 31)
buf[0] = 35
p := 1
p = WriteUint(msg.ID, buf, p)
p = WriteUint(msg.Name, buf, p)
p = WriteUint(msg.Value, buf, p)
return buf[:p]
}
func (msg *SetNodeAttributeDictGlobal) Decode() Message {
return msg
}
func (msg *SetNodeAttributeDictGlobal) TypeID() int {
return 35
}
type CSSInsertRule struct {
message
ID uint64

View file

@ -606,6 +606,33 @@ func DecodePageEvent(reader BytesReader) (Message, error) {
return msg, err
}
func DecodeStringDictGlobal(reader BytesReader) (Message, error) {
var err error = nil
msg := &StringDictGlobal{}
if msg.Key, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.Value, err = reader.ReadString(); err != nil {
return nil, err
}
return msg, err
}
func DecodeSetNodeAttributeDictGlobal(reader BytesReader) (Message, error) {
var err error = nil
msg := &SetNodeAttributeDictGlobal{}
if msg.ID, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.Name, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.Value, err = reader.ReadUint(); err != nil {
return nil, err
}
return msg, err
}
func DecodeCSSInsertRule(reader BytesReader) (Message, error) {
var err error = nil
msg := &CSSInsertRule{}
@ -2123,6 +2150,10 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
return DecodeInputEvent(reader)
case 33:
return DecodePageEvent(reader)
case 34:
return DecodeStringDictGlobal(reader)
case 35:
return DecodeSetNodeAttributeDictGlobal(reader)
case 37:
return DecodeCSSInsertRule(reader)
case 38:

2
ee/api/.gitignore vendored
View file

@ -222,7 +222,7 @@ Pipfile.lock
/chalicelib/core/sessions/performance_event.py
/chalicelib/core/sessions/sessions_viewed.py
/chalicelib/core/sessions/unprocessed_sessions.py
/chalicelib/core/significance.py
/chalicelib/core/metrics/modules
/chalicelib/core/socket_ios.py
/chalicelib/core/sourcemaps.py
/chalicelib/core/sourcemaps_parser.py

View file

@ -266,16 +266,16 @@ def __search_metadata(project_id, value, key=None, source=None):
TYPE_TO_COLUMN = {
schemas.EventType.CLICK: "label",
schemas.EventType.INPUT: "label",
schemas.EventType.LOCATION: "url_path",
schemas.EventType.CUSTOM: "name",
schemas.FetchFilterType.FETCH_URL: "url_path",
schemas.GraphqlFilterType.GRAPHQL_NAME: "name",
schemas.EventType.STATE_ACTION: "name",
schemas.EventType.CLICK: "`$properties`.label",
schemas.EventType.INPUT: "`$properties`.label",
schemas.EventType.LOCATION: "`$properties`.url_path",
schemas.EventType.CUSTOM: "`$properties`.name",
schemas.FetchFilterType.FETCH_URL: "`$properties`.url_path",
schemas.GraphqlFilterType.GRAPHQL_NAME: "`$properties`.name",
schemas.EventType.STATE_ACTION: "`$properties`.name",
# For ERROR, sessions search is happening over name OR message,
# for simplicity top 10 is using name only
schemas.EventType.ERROR: "name",
schemas.EventType.ERROR: "`$properties`.name",
schemas.FilterType.USER_COUNTRY: "user_country",
schemas.FilterType.USER_CITY: "user_city",
schemas.FilterType.USER_STATE: "user_state",
@ -325,9 +325,9 @@ def get_top_values(project_id, event_type, event_key=None):
query = f"""WITH raw AS (SELECT DISTINCT {colname} AS c_value,
COUNT(1) OVER (PARTITION BY c_value) AS row_count,
COUNT(1) OVER () AS total_count
FROM experimental.events
FROM product_analytics.events
WHERE project_id = %(project_id)s
AND event_type = '{event_type}'
AND `$event_name` = '{event_type}'
AND isNotNull(c_value)
AND notEmpty(c_value)
ORDER BY row_count DESC

View file

@ -1,9 +1,7 @@
import json
import logging
from decouple import config
from chalicelib.utils.storage import extra
from chalicelib.core.sessions import sessions_mobs, sessions_favorite
from chalicelib.utils.storage import extra
from .custom_metrics import *
@ -14,8 +12,8 @@ def create_card(project: schemas.ProjectContext, user_id, data: schemas.CardSche
if data.session_id is not None:
session_data = {"sessionId": data.session_id}
else:
session_data = __get_heat_map_chart(project=project, user_id=user_id,
data=data, include_mobs=False)
session_data = get_heat_map_chart(project=project, user_id=user_id,
data=data, include_mobs=False)
if session_data is not None:
session_data = {"sessionId": session_data["sessionId"]}
@ -42,8 +40,10 @@ def create_card(project: schemas.ProjectContext, user_id, data: schemas.CardSche
series_len = len(data.series)
params = {"user_id": user_id, "project_id": project.project_id, **data.model_dump(), **_data,
"default_config": json.dumps(data.default_config.model_dump()), "card_info": None}
params["card_info"] = get_global_card_info(data=data)
if data.metric_type == schemas.MetricType.PATH_ANALYSIS:
params["card_info"] = json.dumps(__get_path_analysis_card_info(data=data))
params["card_info"] = {**params["card_info"], **get_path_analysis_card_info(data=data)}
params["card_info"] = json.dumps(params["card_info"])
query = """INSERT INTO metrics (project_id, user_id, name, is_public,
view_type, metric_type, metric_of, metric_value,

View file

@ -13,9 +13,10 @@ if config("EXP_7D_MV", cast=bool, default=True):
def get_main_events_table(timestamp=0, platform="web"):
if platform == "web":
return "experimental.events_l7d_mv" \
if config("EXP_7D_MV", cast=bool, default=True) \
and timestamp and timestamp >= TimeUTC.now(delta_days=-7) else "experimental.events"
return "product_analytics.events"
# return "experimental.events_l7d_mv" \
# if config("EXP_7D_MV", cast=bool, default=True) \
# and timestamp and timestamp >= TimeUTC.now(delta_days=-7) else "experimental.events"
else:
return "experimental.ios_events"

View file

@ -43,7 +43,7 @@ rm -rf ./chalicelib/core/sessions/sessions_search.py
rm -rf ./chalicelib/core/sessions/performance_event.py
rm -rf ./chalicelib/core/sessions/sessions_viewed.py
rm -rf ./chalicelib/core/sessions/unprocessed_sessions.py
rm -rf ./chalicelib/core/significance.py
rm -rf ./chalicelib/core/metrics/modules
rm -rf ./chalicelib/core/socket_ios.py
rm -rf ./chalicelib/core/sourcemaps.py
rm -rf ./chalicelib/core/sourcemaps_parser.py

View file

@ -1,7 +1,7 @@
from typing import Union
import schemas
from chalicelib.core.metrics import dashboards, custom_metrics
from chalicelib.core.metrics import custom_metrics, dashboards
from fastapi import Body, Depends
from or_dependencies import OR_context, OR_scope
from routers.base import get_routers
@ -87,7 +87,7 @@ def try_card(projectId: int, data: schemas.CardSchema = Body(...),
@app.post('/{projectId}/cards/try/sessions', tags=["cards"])
def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
data = custom_metrics.get_sessions(project_id=projectId, user_id=context.user_id, data=data)
data = custom_metrics.get_sessions(project=context.project, user_id=context.user_id, data=data)
return {"data": data}
@ -130,7 +130,7 @@ def get_card(projectId: int, metric_id: Union[int, str], context: schemas.Curren
def get_card_sessions(projectId: int, metric_id: int,
data: schemas.CardSessionsSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
data = custom_metrics.get_sessions_by_card_id(project_id=projectId, user_id=context.user_id, metric_id=metric_id,
data = custom_metrics.get_sessions_by_card_id(project=context.project, user_id=context.user_id, metric_id=metric_id,
data=data)
if data is None:
return {"errors": ["custom metric not found"]}

View file

@ -334,3 +334,6 @@ CREATE TABLE IF NOT EXISTS product_analytics.all_properties
_timestamp DateTime DEFAULT now()
) ENGINE = ReplacingMergeTree(_timestamp)
ORDER BY (project_id, property_name, is_event_property);
DROP TABLE IF EXISTS experimental.events_l7d_mv;

View file

@ -191,78 +191,6 @@ CREATE TABLE IF NOT EXISTS experimental.issues
ORDER BY (project_id, issue_id, type)
TTL _timestamp + INTERVAL 3 MONTH;
CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.events_l7d_mv
ENGINE = ReplacingMergeTree(_timestamp)
PARTITION BY toYYYYMMDD(datetime)
ORDER BY (project_id, datetime, event_type, session_id, message_id)
TTL datetime + INTERVAL 7 DAY
POPULATE
AS
SELECT session_id,
project_id,
event_type,
datetime,
label,
hesitation_time,
name,
payload,
level,
source,
message,
error_id,
duration,
context,
url,
url_host,
url_path,
url_hostpath,
request_start,
response_start,
response_end,
dom_content_loaded_event_start,
dom_content_loaded_event_end,
load_event_start,
load_event_end,
first_paint,
first_contentful_paint_time,
speed_index,
visually_complete,
time_to_interactive,
ttfb,
ttlb,
response_time,
dom_building_time,
dom_content_loaded_event_time,
load_event_time,
min_fps,
avg_fps,
max_fps,
min_cpu,
avg_cpu,
max_cpu,
min_total_js_heap_size,
avg_total_js_heap_size,
max_total_js_heap_size,
min_used_js_heap_size,
avg_used_js_heap_size,
max_used_js_heap_size,
method,
status,
success,
request_body,
response_body,
issue_type,
issue_id,
error_tags_keys,
error_tags_values,
transfer_size,
selector,
normalized_x,
normalized_y,
message_id,
_timestamp
FROM experimental.events
WHERE datetime >= now() - INTERVAL 7 DAY;
CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.sessions_l7d_mv

View file

@ -1 +1,75 @@
CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.21.0-ee';
CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.events_l7d_mv
ENGINE = ReplacingMergeTree(_timestamp)
PARTITION BY toYYYYMMDD(datetime)
ORDER BY (project_id, datetime, event_type, session_id, message_id)
TTL datetime + INTERVAL 7 DAY
POPULATE
AS
SELECT session_id,
project_id,
event_type,
datetime,
label,
hesitation_time,
name,
payload,
level,
source,
message,
error_id,
duration,
context,
url,
url_host,
url_path,
url_hostpath,
request_start,
response_start,
response_end,
dom_content_loaded_event_start,
dom_content_loaded_event_end,
load_event_start,
load_event_end,
first_paint,
first_contentful_paint_time,
speed_index,
visually_complete,
time_to_interactive,
ttfb,
ttlb,
response_time,
dom_building_time,
dom_content_loaded_event_time,
load_event_time,
min_fps,
avg_fps,
max_fps,
min_cpu,
avg_cpu,
max_cpu,
min_total_js_heap_size,
avg_total_js_heap_size,
max_total_js_heap_size,
min_used_js_heap_size,
avg_used_js_heap_size,
max_used_js_heap_size,
method,
status,
success,
request_body,
response_body,
issue_type,
issue_id,
error_tags_keys,
error_tags_values,
transfer_size,
selector,
normalized_x,
normalized_y,
message_id,
_timestamp
FROM experimental.events
WHERE datetime >= now() - INTERVAL 7 DAY;

View file

@ -18,11 +18,6 @@ const components: any = {
AssistPure: lazy(() => import('Components/Assist/AssistRouter')),
SessionsOverviewPure: lazy(() => import('Components/Overview')),
DashboardPure: lazy(() => import('Components/Dashboard/NewDashboard')),
FunnelDetailsPure: lazy(() => import('Components/Funnels/FunnelDetails')),
FunnelIssueDetails: lazy(
() => import('Components/Funnels/FunnelIssueDetails')
),
FunnelPagePure: lazy(() => import('Components/Funnels/FunnelPage')),
MultiviewPure: lazy(() => import('Components/Session_/Multiview/Multiview')),
UsabilityTestingPure: lazy(
() => import('Components/UsabilityTesting/UsabilityTesting')
@ -47,9 +42,6 @@ const enhancedComponents: any = {
Assist: withSiteIdUpdater(components.AssistPure),
Client: withSiteIdUpdater(components.ClientPure),
Onboarding: withSiteIdUpdater(components.OnboardingPure),
FunnelPage: withSiteIdUpdater(components.FunnelPagePure),
FunnelsDetails: withSiteIdUpdater(components.FunnelDetailsPure),
FunnelIssue: withSiteIdUpdater(components.FunnelIssueDetails),
Multiview: withSiteIdUpdater(components.MultiviewPure),
UsabilityTesting: withSiteIdUpdater(components.UsabilityTestingPure),
UsabilityTestEdit: withSiteIdUpdater(components.UsabilityTestEditPure),
@ -85,9 +77,6 @@ const FFLAG_READ_PATH = routes.fflagRead();
const NOTES_PATH = routes.notes();
const BOOKMARKS_PATH = routes.bookmarks();
const RECORDINGS_PATH = routes.recordings();
const FUNNEL_PATH = routes.funnels();
const FUNNEL_CREATE_PATH = routes.funnelsCreate();
const FUNNEL_ISSUE_PATH = routes.funnelIssue();
const SESSION_PATH = routes.session();
const CLIENT_PATH = routes.client();
const ONBOARDING_PATH = routes.onboarding();
@ -246,24 +235,6 @@ function PrivateRoutes() {
path={withSiteId(HIGHLIGHTS_PATH, siteIdList)}
component={enhancedComponents.Highlights}
/>
<Route
exact
strict
path={withSiteId(FUNNEL_PATH, siteIdList)}
component={enhancedComponents.FunnelPage}
/>
<Route
exact
strict
path={withSiteId(FUNNEL_CREATE_PATH, siteIdList)}
component={enhancedComponents.FunnelsDetails}
/>
<Route
exact
strict
path={withSiteId(FUNNEL_ISSUE_PATH, siteIdList)}
component={enhancedComponents.FunnelIssue}
/>
<Route
exact
strict

View file

@ -1,11 +1,10 @@
import React, { useEffect } from 'react';
import stl from './notifications.module.css';
import { Icon, Tooltip } from 'UI';
import { Icon } from 'UI';
import { useModal } from 'App/components/Modal';
import AlertTriggersModal from 'Shared/AlertTriggersModal';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Badge, Button } from 'antd';
import { Badge, Button, Tooltip } from 'antd';
import { BellOutlined } from '@ant-design/icons';

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Button } from 'antd';
import { Button, Tooltip } from 'antd';
import { useModal } from 'App/components/Modal';
import { MODULES } from 'Components/Client/Modules';
@ -26,22 +26,24 @@ function AssistSearchActions() {
};
return (
<div className="flex items-center w-full gap-2">
{isEnterprise && modules.includes(MODULES.OFFLINE_RECORDINGS)
? <Button type="primary" ghost onClick={showRecords}>Training Videos</Button> : null
{isEnterprise && !modules.includes(MODULES.OFFLINE_RECORDINGS)
? <Button type="text" onClick={showRecords}>Training Videos</Button> : null
}
{isEnterprise && (
<Button type="primary" ghost onClick={showStats}
{isEnterprise && userStore.account?.admin && (
<Button type="text" onClick={showStats}
disabled={modules.includes(MODULES.ASSIST_STATS) || modules.includes(MODULES.ASSIST)}>
Co-Browsing Reports</Button>
)}
<Button
type="link"
className="ml-auto font-medium"
disabled={!hasFilters && !hasEvents}
onClick={() => searchStoreLive.clearSearch()}
>
Clear Search
</Button>
<Tooltip title='Clear Search Filters'>
<Button
type="text"
disabled={!hasFilters && !hasEvents}
onClick={() => searchStoreLive.clearSearch()}
className="px-2 ml-auto"
>
Clear
</Button>
</Tooltip>
</div>
);
}

View file

@ -1,10 +1,10 @@
import React, { useState } from 'react'
import stl from './ChatControls.module.css'
import cn from 'classnames'
import { Button, Icon } from 'UI'
import { Icon } from 'UI'
import { Button } from 'antd'
import type { LocalStream } from 'Player';
interface Props {
stream: LocalStream | null,
endCall: () => void,
@ -35,17 +35,15 @@ function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled, isPresta
return (
<div className={cn(stl.controls, "flex items-center w-full justify-start bottom-0 px-2")}>
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className={cn(stl.btnWrapper, { [stl.disabled]: audioEnabled})}>
<Button variant="text" onClick={toggleAudio} hover>
<Icon name={audioEnabled ? 'mic' : 'mic-mute'} size="16" />
<Button size={'small'} variant="text" onClick={toggleAudio} icon={<Icon name={audioEnabled ? 'mic' : 'mic-mute'} size="16" />}>
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : audioEnabled })}>{audioEnabled ? 'Mute' : 'Unmute'}</span>
</Button>
</div>
<div className={cn(stl.btnWrapper, { [stl.disabled]: videoEnabled})}>
<Button variant="text" onClick={toggleVideo} hover>
<Icon name={ videoEnabled ? 'camera-video' : 'camera-video-off' } size="16" />
<Button size={'small'} variant="text" onClick={toggleVideo} icon={<Icon name={ videoEnabled ? 'camera-video' : 'camera-video-off' } size="16" />}>
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : videoEnabled })}>{videoEnabled ? 'Stop Video' : 'Start Video'}</span>
</Button>
</div>

View file

@ -1,6 +1,7 @@
import { useObserver } from 'mobx-react-lite';
import React from 'react';
import { Button, Modal, Form, Icon, Input } from 'UI';
import { Modal, Form, Icon, Input } from 'UI';
import { Button } from 'antd'
interface Props {
show: boolean;
@ -57,7 +58,7 @@ function EditRecordingModal(props: Props) {
<Modal.Footer>
<div className="-mx-2 px-2">
<Button
variant="primary"
type="primary"
onClick={ save }
className="float-left mr-2"
>

View file

@ -1,6 +1,7 @@
import React from 'react';
import { INDEXES } from 'App/constants/zindex';
import { Button, Loader, Icon } from 'UI';
import { Loader, Icon } from 'UI';
import { Button } from 'antd'
import { PlayerContext } from 'App/components/Session/playerContext';
import { useStore } from "App/mstore";
import { observer } from 'mobx-react-lite';
@ -76,7 +77,7 @@ function RequestingWindow({ getWindowType }: Props) {
</div>
<span>{WIN_VARIANTS[windowType].text}</span>
<Loader size={30} style={{ minHeight: 60 }} />
<Button variant="text-primary" onClick={actions[WIN_VARIANTS[windowType].action]}>
<Button variant="text" onClick={actions[WIN_VARIANTS[windowType].action]}>
Cancel
</Button>
</div>

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Button, Tooltip } from 'UI';
import { Button } from 'antd';
import cn from 'classnames';
import ChatWindow from '../../ChatWindow';
import { CallingState, ConnectionStatus, RemoteControlStatus, RequestLocalStream } from 'Player';
@ -7,7 +7,7 @@ import type { LocalStream } from 'Player';
import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import { toast } from 'react-toastify';
import { confirm } from 'UI';
import { confirm, Icon, Tooltip } from 'UI';
import stl from './AassistActions.module.css';
import ScreenRecorder from 'App/components/Session_/ScreenRecorder/ScreenRecorder';
import { audioContextManager } from 'App/utils/screenRecorder';
@ -221,9 +221,10 @@ function AssistActions({
role="button"
>
<Button
icon={annotating ? 'pencil-stop' : 'pencil'}
variant={annotating ? 'text-red' : 'text-primary'}
icon={<Icon name={annotating ? 'pencil-stop' : 'pencil'} size={16} />}
type={'text'}
style={{ height: '28px' }}
className={annotating ? 'text-red' : 'text-main'}
>
Annotate
</Button>
@ -246,8 +247,9 @@ function AssistActions({
role="button"
>
<Button
icon={remoteActive ? 'window-x' : 'remote-control'}
variant={remoteActive ? 'text-red' : 'text-primary'}
icon={<Icon name={remoteActive ? 'window-x' : 'remote-control'} size={16} />}
type={'text'}
className={remoteActive ? 'text-red' : 'text-main'}
style={{ height: '28px' }}
>
Remote Control
@ -272,8 +274,9 @@ function AssistActions({
role="button"
>
<Button
icon="headset"
variant={onCall ? 'text-red' : isPrestart ? 'green' : 'primary'}
icon={<Icon name={'headset'} size={16} />}
type={'text'}
className={onCall ? 'text-red' : isPrestart ? 'text-green' : 'text-main'}
style={{ height: '28px' }}
>
{onCall ? 'End' : isPrestart ? 'Join Call' : 'Call'}

View file

@ -13,6 +13,7 @@ echarts.use([BarChart]);
interface BarChartProps extends DataProps {
label?: string;
onClick?: (event: any) => void;
onSeriesFocus?: (event: any) => void;
}
function ORBarChart(props: BarChartProps) {
@ -81,6 +82,9 @@ function ORBarChart(props: BarChartProps) {
const index = event.dataIndex;
const timestamp = (window as any).__timestampMap?.[chartUuid.current]?.[index];
props.onClick?.({ activePayload: [{ payload: { timestamp }}]})
setTimeout(() => {
props.onSeriesFocus?.(event.seriesName)
}, 0)
})
return () => {

View file

@ -12,6 +12,7 @@ interface Props extends DataProps {
isArea?: boolean;
chartName?: string;
onClick?: (event: any) => void;
onSeriesFocus?: (event: any) => void;
}
function ORLineChart(props: Props) {
@ -73,7 +74,8 @@ function ORLineChart(props: Props) {
// nameGap: 40,
nameTextStyle: {
padding: [0, 0, 0, 15],
}
},
minInterval: 1
},
tooltip: {
...defaultOptions.tooltip,
@ -91,6 +93,9 @@ function ORLineChart(props: Props) {
const index = event.dataIndex;
const timestamp = (window as any).__timestampMap?.[chartUuid.current]?.[index];
props.onClick?.({ activePayload: [{ payload: { timestamp }}]})
setTimeout(() => {
props.onSeriesFocus?.(event.seriesName)
}, 0)
})
return () => {

View file

@ -36,6 +36,7 @@ const defaultOptions = {
extraCssText: 'box-shadow: none; pointer-events: auto;',
axisPointer: {
type: 'cross',
snap: true,
label: {
backgroundColor: '#6a7985'
},

View file

@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { PageTitle, Icon, Button } from 'UI';
import { PageTitle, Icon } from 'UI';
import { Button } from 'antd'
import AuditList from '../AuditList';
import AuditSearchField from '../AuditSearchField';
import { useStore } from 'App/mstore';
@ -61,8 +62,7 @@ function AuditView() {
auditStore.updateKey('page', 1)
} }/>
<div>
<Button variant="text-primary" className="ml-3" onClick={exportToCsv}>
<Icon name="grid-3x3" color="teal" />
<Button type="text" icon={<Icon name="grid-3x3" color="teal" />} className="ml-3" onClick={exportToCsv}>
<span className="ml-2">Export to CSV</span>
</Button>
</div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import cn from 'classnames';
import { Button } from 'UI';
import { Icon } from 'UI';
import { Button } from 'antd';
import styles from './listItem.module.css';
const ListItem = ({ field, onEdit, disabled }) => {
@ -17,7 +18,7 @@ const ListItem = ({ field, onEdit, disabled }) => {
>
<span>{field.key}</span>
<div className="invisible group-hover:visible" data-hidden={field.index === 0}>
<Button variant="text-primary" icon="pencil" />
<Button type="text" icon={<Icon name={"pencil"} size={16} />} />
</div>
</div>
);

View file

@ -3,7 +3,8 @@ import React from 'react';
import { useStore } from 'App/mstore';
import { namedStore } from 'App/mstore/integrationsStore';
import { Button, Checkbox, Form, Input, Loader } from 'UI';
import { Checkbox, Form, Input, Loader } from 'UI';
import { Button } from 'antd'
import { toast } from 'react-toastify';
function IntegrationForm(props: any) {
@ -97,7 +98,7 @@ function IntegrationForm(props: any) {
onClick={save}
disabled={!config?.validate()}
loading={loading}
variant="primary"
type="primary"
className="float-left mr-2"
>
{config?.exists() ? 'Update' : 'Add'}

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Form, Input, Button, Message } from 'UI';
import { confirm } from 'UI';
import { Form, Input, Message, confirm } from 'UI';
import { Button } from 'antd'
import { observer } from 'mobx-react-lite'
import { useStore } from 'App/mstore'
@ -73,7 +73,7 @@ function SlackAddForm(props) {
onClick={save}
disabled={!instance.validate()}
loading={saving}
variant="primary"
type="primary"
className="float-left mr-2"
>
{instance.exists() ? 'Update' : 'Add'}

View file

@ -1,7 +1,8 @@
import React, { useEffect } from 'react';
import SlackChannelList from './SlackChannelList/SlackChannelList';
import SlackAddForm from './SlackAddForm';
import { Button } from 'UI';
import { Icon } from 'UI';
import { Button } from 'antd';
import { observer } from 'mobx-react-lite'
import { useStore } from 'App/mstore'
@ -34,7 +35,7 @@ const SlackForm = () => {
<div className="shrink-0" style={{ width: '350px' }}>
<div className="flex items-center p-5">
<h3 className="text-2xl mr-3">Slack</h3>
<Button rounded={true} icon="plus" iconSize={24} variant="outline" onClick={onNew}/>
<Button shape={'circle'} type={'text'} icon={<Icon name={"plus"} size={24} />} onClick={onNew}/>
</div>
<SlackChannelList onEdit={onEdit} />
</div>

View file

@ -2,8 +2,8 @@ import { observer } from 'mobx-react-lite';
import React from 'react';
import { useStore } from 'App/mstore';
import { Button, Form, Input, Message } from 'UI';
import { confirm } from 'UI';
import { confirm, Form, Input, Message } from 'UI';
import { Button } from 'antd'
interface Props {
onClose: () => void;
@ -83,7 +83,7 @@ function TeamsAddForm({ onClose }: Props) {
onClick={save}
disabled={!instance?.validate()}
loading={saving}
variant="primary"
type="primary"
className="float-left mr-2"
>
{instance?.exists() ? 'Update' : 'Add'}

View file

@ -1,10 +1,11 @@
import React, { useEffect } from 'react';
import TeamsChannelList from './TeamsChannelList';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Icon } from 'UI';
import { Button } from 'antd'
import TeamsChannelList from './TeamsChannelList';
import TeamsAddForm from './TeamsAddForm';
import { Button } from 'UI';
const MSTeams = () => {
const { integrationsStore } = useStore();
@ -35,7 +36,7 @@ const MSTeams = () => {
<div className="shrink-0" style={{ width: '350px' }}>
<div className="flex items-center p-5">
<h3 className="text-2xl mr-3">Microsoft Teams</h3>
<Button rounded={true} icon="plus" iconSize={24} variant="outline" onClick={onNew}/>
<Button shape={'circle'} icon={<Icon name={'plus'} size={24} />} type="text" onClick={onNew}/>
</div>
<TeamsChannelList onEdit={onEdit} />
</div>

View file

@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import cn from 'classnames';
import stl from './notifications.module.css';
import { Toggler } from 'UI';
import { Switch } from 'antd'
import { useStore } from "App/mstore";
import { observer } from 'mobx-react-lite'
import withPageTitle from 'HOCs/withPageTitle';
@ -25,12 +25,13 @@ function Notifications() {
<div className="">
<div className="text-lg font-medium">Weekly project summary</div>
<div className="mb-4">Receive weekly report for each project on email.</div>
<Toggler
<div className={'flex items-center gap-2'}>
<Switch
checked={weeklyReportStore.weeklyReport}
name="test"
onChange={onChange}
label={weeklyReportStore.weeklyReport ? 'Yes' : 'No'}
/>
<span>{weeklyReportStore.weeklyReport ? 'Yes' : 'No'}</span>
</div>
</div>
</div>
);

View file

@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react';
import { Button, Message, Form, Input } from 'UI';
import { Message, Form, Input } from 'UI';
import { Button } from 'antd'
import styles from './profileSettings.module.css';
import { validatePassword } from 'App/validate';
import { PASSWORD_POLICY } from 'App/constants';
@ -121,7 +122,7 @@ const ChangePassword = () => {
{PASSWORD_POLICY}
</Message>
<div className="flex items-center pt-3">
<Button type="submit" variant="outline" disabled={isSubmitDisabled()} loading={loading}>
<Button htmlType="submit" type="default" disabled={isSubmitDisabled()} loading={loading}>
Change Password
</Button>
<Button
@ -139,7 +140,7 @@ const ChangePassword = () => {
</Form>
) : (
<div onClick={() => setShow(true)}>
<Button variant="text-primary">Change Password</Button>
<Button type="text">Change Password</Button>
</div>
);
};

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Button, Input, Form } from 'UI';
import { Input, Form } from 'UI';
import { Button } from 'antd'
import styles from './profileSettings.module.css';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
@ -61,7 +62,7 @@ function Settings() {
/>
</Form.Field>
<Button variant="outline" loading={loading} disabled={!changed} type="submit">
<Button type="default" loading={loading} disabled={!changed} htmlType="submit">
{'Update'}
</Button>
</Form>

View file

@ -1,6 +1,7 @@
import React from 'react';
import copy from 'copy-to-clipboard';
import { Form, Input, Button } from "UI";
import { Form, Input } from "UI";
import { Button } from 'antd';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
@ -29,8 +30,7 @@ function TenantKey() {
value={ tenantKey }
leadingButton={
<Button
variant="text-primary"
role="button"
type="text"
onClick={ copyHandler }
>
{ copied ? 'Copied' : 'Copy' }

View file

@ -5,8 +5,8 @@ import React, { useEffect } from 'react';
import { useModal } from 'App/components/Modal';
import { useStore } from 'App/mstore';
import { Button, Loader, NoContent, Tooltip } from 'UI';
import { confirm } from 'UI';
import { Loader, NoContent, Tooltip, confirm } from 'UI';
import { Button } from 'antd'
import RoleForm from './components/RoleForm';
import RoleItem from './components/RoleItem';
@ -68,7 +68,7 @@ function Roles() {
title="You dont have the permissions to perform this action."
disabled={isAdmin}
>
<Button variant="primary" onClick={() => editHandler({})}>
<Button type="primary" onClick={() => editHandler({})}>
Add
</Button>
</Tooltip>

View file

@ -2,10 +2,10 @@ import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef } from 'react';
import { useStore } from 'App/mstore';
import { Button, Checkbox, Form, Icon, Input } from 'UI';
import { Checkbox, Form, Icon, Input } from 'UI';
import stl from './roleForm.module.css';
import { Select } from 'antd';
import { Select, Button } from 'antd';
import { SelectProps } from 'antd/es/select';
interface Props {
@ -220,7 +220,7 @@ const RoleForm = (props: Props) => {
onClick={_save}
disabled={!role.validate}
loading={saving}
variant="primary"
type="primary"
className="float-left mr-2"
>
{role.exists() ? 'Update' : 'Add'}
@ -228,7 +228,7 @@ const RoleForm = (props: Props) => {
{role.exists() && <Button onClick={closeModal}>{'Cancel'}</Button>}
</div>
{role.exists() && (
<Button variant="text" onClick={() => props.deleteHandler(role)}>
<Button type="text" onClick={() => props.deleteHandler(role)}>
<Icon name="trash" size="18" />
</Button>
)}

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Icon, Link, Button } from 'UI';
import { Icon, Link } from 'UI';
import { Button } from 'antd'
import stl from './roleItem.module.css';
import cn from 'classnames';
import { CLIENT_TABS, client as clientRoute } from 'App/routes';
@ -46,7 +47,7 @@ function RoleItem({ role, editHandler, isAdmin, permissions, projects }: Props)
<div className={cn(stl.actions, 'absolute right-0 top-0 bottom-0 mr-8 invisible group-hover:visible')}>
{isAdmin && !!editHandler && (
<Button variant="text-primary" icon="pencil" disabled={role.protected} onClick={() => editHandler(role)} />
<Button type="text" icon={<Icon name={"pencil"} />} disabled={role.protected} onClick={() => editHandler(role)} />
)}
</div>
</div>

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Tooltip, Button } from 'UI';
import { Tooltip } from 'UI';
import { Button } from 'antd'
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { useModal } from 'App/components/Modal';
@ -24,7 +25,7 @@ function AddProjectButton({ isAdmin = false }: any) {
title={`${!isAdmin ? PERMISSION_WARNING : !canAddProject ? LIMIT_WARNING : 'Add a Project'}`}
disabled={isAdmin || canAddProject}
>
<Button variant="primary" onClick={onClick} disabled={!canAddProject || !isAdmin}>
<Button type="primary" onClick={onClick} disabled={!canAddProject || !isAdmin}>
Add Project
</Button>
</Tooltip>

View file

@ -1,31 +0,0 @@
import React from 'react';
import { Input, Button, Icon } from 'UI';
import styles from './blockedIps.module.css';
class BlockedIps extends React.PureComponent {
render() {
return (
<div className={ styles.wrapper }>
<h4>{ 'Block IP' }</h4>
<div className={ styles.content }>
<label>{ 'List of IPs or Subnets to be blocked.' }</label>
<div className={ styles.inputWrapper }>
<Input type="text" />
<Button primary outline >{ 'Block' }</Button>
</div>
<div className={ styles.list }>
<div className={ styles.item }>
<div>{ '192.128.2.1' }</div>
<div className={ styles.actions }>
<Icon name="trash" size="14" color="gray-medium" />
</div>
</div>
</div>
</div>
</div>
);
}
}
export default BlockedIps;

View file

@ -1,7 +1,8 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from "App/mstore";
import { Form, Button, Input, Icon } from 'UI';
import { Form, Input, Icon } from 'UI';
import { Button } from 'antd'
import { validateNumber } from 'App/validate';
import styles from './siteForm.module.css';
import Select from 'Shared/Select';
@ -115,12 +116,13 @@ function GDPRForm(props) {
<div className={ styles.footer }>
<Button
variant="outline"
type="primary"
className="float-left mr-2"
loading={ saving }
content="Update"
/>
<Button onClick={ onClose } content="Cancel" />
>
Update
</Button>
<Button onClick={ onClose }>Close</Button>
</div>
</Form>
)

View file

@ -1,7 +1,7 @@
import { useModal } from 'App/components/Modal';
import React from 'react';
import TrackingCodeModal from 'Shared/TrackingCodeModal';
import { Button } from 'UI';
import { Button } from 'antd';
interface Props {
site: any;
@ -16,7 +16,7 @@ function InstallButton(props: Props) {
);
};
return (
<Button size="small" variant="text-primary" onClick={onClick}>
<Button size="small" type="text" onClick={onClick}>
{'Installation Steps'}
</Button>
);

View file

@ -3,8 +3,8 @@ import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { toast } from 'react-toastify';
import { useStore } from 'App/mstore';
import { Button, Form, Icon, Input } from 'UI';
import { confirm } from 'UI';
import { confirm, Form, Icon, Input } from 'UI';
import { Button } from 'antd'
import { observer } from 'mobx-react-lite';
import styles from './siteForm.module.css';
@ -141,8 +141,8 @@ const NewSiteForm = ({ location: { pathname }, onClose }: Props) => {
</Form.Field>
<div className="mt-6 flex justify-between">
<Button
variant="primary"
type="submit"
type="primary"
htmlType="submit"
className="float-left mr-2"
loading={loading}
disabled={!site.validate}
@ -151,8 +151,7 @@ const NewSiteForm = ({ location: { pathname }, onClose }: Props) => {
</Button>
{site.exists() && (
<Button
variant="text"
type="button"
type="text"
onClick={handleRemove}
disabled={!canDelete}
>

View file

@ -1,7 +1,8 @@
import React, { useState } from 'react';
import { Tag } from 'antd';
import cn from 'classnames';
import { Loader, Button, TextLink, NoContent, Pagination, PageTitle, Divider, Icon } from 'UI';
import { Loader, TextLink, NoContent, Pagination, PageTitle, Divider, Icon } from 'UI';
import { Button } from 'antd';
import withPageTitle from 'HOCs/withPageTitle';
import stl from './sites.module.css';
import NewSiteForm from './NewSiteForm';
@ -52,7 +53,7 @@ const Sites = () => {
showModal(<NewSiteForm onClose={hideModal} />, { right: true });
};
return <Button icon="edit" variant="text-primary" disabled={!isAdmin} onClick={_onClick} />;
return <Button icon={<Icon name={"edit"} />} type="text" disabled={!isAdmin} onClick={_onClick} />;
};
const captureRateClickHandler = (project: Project) => {
@ -88,12 +89,12 @@ const Sites = () => {
<ProjectKey value={project.projectKey} tooltip="Project key copied to clipboard" />
</div>
<div className="col-span-3 flex items-center">
<Button variant="text-primary" onClick={() => captureRateClickHandler(project)}>
<Button type="text" onClick={() => captureRateClickHandler(project)}>
{project.sampleRate}%
</Button>
{project.conditionsCount > 0 ? (
<Button
variant="text-primary"
variant="text"
onClick={() => captureRateClickHandler(project)}
className="ml-2"
>

View file

@ -1,37 +0,0 @@
.wrapper {
width: 300px;
padding: 20px;
& label {
color: $gray-medium;
font-size: 12px;
}
}
.inputWrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.list {
margin-top: 20px;
display: none; /* TODO enable this once the API is Ready */
& .item {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: solid thin $gray-light;
padding: 8px 5px;
&:hover {
background-color: $active-blue;
& .actions {
opacity: 1;
}
}
& .actions {
opacity: 0;
}
}
}

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Tooltip, Button } from 'UI';
import { Tooltip } from 'UI';
import { Button } from 'antd'
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
@ -17,7 +18,7 @@ function AddUserButton({ isAdmin = false, onClick, btnVariant = 'primary' }: any
title={`${!isAdmin ? PERMISSION_WARNING : !cannAddUser ? LIMIT_WARNING : 'Add team member'}`}
disabled={isAdmin || cannAddUser}
>
<Button disabled={!cannAddUser || !isAdmin} variant={btnVariant} onClick={onClick}>
<Button disabled={!cannAddUser || !isAdmin} type={btnVariant} onClick={onClick}>
Add Team Member
</Button>
</Tooltip>

View file

@ -5,8 +5,8 @@ import React from 'react';
import { useModal } from 'App/components/Modal';
import { useStore } from 'App/mstore';
import { Button, CopyButton, Form, Icon, Input } from 'UI';
import { confirm } from 'UI';
import { confirm, CopyButton, Form, Icon, Input } from 'UI';
import { Button } from 'antd'
import Select from 'Shared/Select';
@ -139,22 +139,20 @@ function UserForm() {
onClick={onSave}
disabled={!user.valid(isEnterprise) || isSaving}
loading={isSaving}
variant="primary"
type="primary"
className="float-left mr-2"
>
{user.exists() ? 'Update' : 'Invite'}
</Button>
{user.exists() && <Button onClick={hideModal}>{'Cancel'}</Button>}
</div>
<div>
<Button
disabled={user.isSuperAdmin}
data-hidden={!user.exists()}
onClick={deleteHandler}
>
<Icon name="trash" size="16" />
</Button>
</div>
{!user.exists() ? null :
<div>
<Button disabled={user.isSuperAdmin} onClick={deleteHandler}>
<Icon name="trash" size="16" />
</Button>
</div>
}
</div>
{!user.isJoined && user.invitationLink && (

View file

@ -1,6 +1,7 @@
//@ts-nocheck
import React from 'react';
import { Button, Tooltip } from 'UI';
import { Tooltip, Icon } from 'UI';
import { Button } from 'antd'
import { checkForRecent } from 'App/date';
import cn from 'classnames';
@ -79,9 +80,8 @@ function UserListItem(props: Props) {
{!user.isJoined && user.invitationLink && !user.isExpiredInvite && (
<Tooltip title="Copy Invite Code" hideOnClick={true}>
<Button
variant="text-primary"
icon="link-45deg"
className=""
type="text"
icon={<Icon name={"link-45deg"} />}
onClick={copyInviteCode}
/>
</Tooltip>
@ -90,15 +90,14 @@ function UserListItem(props: Props) {
{!user.isJoined && user.isExpiredInvite && (
<Tooltip title="Generate Invite" hideOnClick={true}>
<Button
icon="link-45deg"
variant="text-primary"
className=""
icon={<Icon name={"link-45deg"} />}
variant="text"
onClick={generateInvite}
/>
</Tooltip>
)}
</div>
<Button variant="text-primary" icon="pencil" />
<Button variant="text" icon={<Icon name={"pencil"} />} />
</div>
</div>
</div>

View file

@ -3,6 +3,7 @@ import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { Loader } from 'UI';
import { withSiteId, dashboard, metrics } from "App/routes";
import DashboardRouter from './components/DashboardRouter';
import withPermissions from 'HOCs/withPermissions';
@ -15,13 +16,19 @@ interface RouterProps {
function NewDashboard(props: RouteComponentProps<RouterProps>) {
const { history, match: { params: { siteId, dashboardId } } } = props;
const { dashboardStore } = useStore();
const initId = React.useRef(siteId)
const loading = dashboardStore.isLoading;
const isMetricDetails = history.location.pathname.includes('/metrics/') || history.location.pathname.includes('/metric/');
const isDashboardDetails = history.location.pathname.includes('/dashboard/');
const isAlertsDetails = history.location.pathname.includes('/alert/');
const shouldHideMenu = isMetricDetails || isDashboardDetails || isAlertsDetails;
const isDbMetric = /\/dashboard\/\d+\/metric\/\d+/.test(history.location.pathname);
const isMetricListMetric = /\/metrics\/\d+/.test(history.location.pathname);
useEffect(() => {
if (siteId !== initId.current) {
if (isMetricListMetric) {
history.push(withSiteId(metrics(), siteId))
}
if (isDbMetric) {
history.push(withSiteId(dashboard(), siteId))
}
}
dashboardStore.fetchList().then((resp) => {
if (parseInt(dashboardId) > 0) {
dashboardStore.selectDashboardById(dashboardId);

View file

@ -1,6 +1,6 @@
import React from 'react';
import cn from 'classnames';
import { Button } from 'UI';
import { Button } from 'antd';
import stl from './table.module.css';
export default class Table extends React.PureComponent {
@ -56,7 +56,7 @@ export default class Table extends React.PureComponent {
<div className="w-full flex justify-center mt-2">
<Button
onClick={ this.onLoadMoreClick }
variant="text-primary"
type="text"
>
{ rows.size + ' More' }
</Button>

View file

@ -1,5 +1,6 @@
import React from 'react'
import { Button, Icon } from 'UI'
import { Icon } from 'UI'
import { Button } from 'antd'
interface IBottomButtons {
loading: boolean
@ -14,8 +15,7 @@ function BottomButtons({ loading, instance, deleting, onDelete }: IBottomButtons
<div className="flex items-center">
<Button
loading={loading}
variant="primary"
type="submit"
type="primary"
disabled={loading || !instance.validate()}
id="submit-button"
>
@ -25,10 +25,8 @@ function BottomButtons({ loading, instance, deleting, onDelete }: IBottomButtons
<div>
{instance.exists() && (
<Button
hover
variant="text"
type="text"
loading={deleting}
type="button"
onClick={() => onDelete(instance)}
id="trash-button"
className="!text-teal !fill-teal"

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useStore } from 'App/mstore';
import { observer, useObserver } from 'mobx-react-lite';
import { Button, Loader, NoContent, Pagination } from 'UI';
import { Loader, NoContent, Pagination } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { InfoCircleOutlined } from '@ant-design/icons';
import { debounce } from 'App/utils';
@ -10,7 +10,7 @@ import CardIssueItem from './CardIssueItem';
import SessionsModal from '../SessionsModal';
import { useModal } from 'App/components/Modal';
import Issue from 'App/mstore/types/issue';
import { List } from 'antd';
import { List, Button } from 'antd';
function CardIssues() {
const { metricStore, dashboardStore } = useStore();
@ -104,8 +104,8 @@ function CardIssues() {
)}
</div>
<div className="flex items-center gap-4">
{hasFilters && <Button variant="text-primary" onClick={clearFilters}>Clear Filters</Button>}
<Button variant="text-primary" onClick={() => handleClick()}>All Sessions</Button>
{hasFilters && <Button type="text" onClick={clearFilters}>Clear Filters</Button>}
<Button type="text" onClick={() => handleClick()}>All Sessions</Button>
</div>
</div>

View file

@ -2,7 +2,8 @@ import { useModal } from 'App/components/Modal';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useState } from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import { Loader, Pagination, Button } from 'UI';
import { Loader, Pagination } from 'UI';
import { Button } from 'antd'
import SessionsModal from './SessionsModal';
import CardUserItem from './CardUserItem';
import { useStore } from 'App/mstore';
@ -45,7 +46,7 @@ function CardUserList(props: RouteComponentProps<Props>) {
<div className="flex justify-between">
<h1 className="font-medium text-2xl">Returning users between</h1>
<div>
<Button variant="text-primary">All Sessions</Button>
<Button type="text">All Sessions</Button>
</div>
</div>

View file

@ -53,7 +53,7 @@ function DashboardEditModal(props: Props) {
value={ dashboard.name }
onChange={write}
placeholder="Title"
maxLength={100}
maxLength={40}
autoFocus={focusTitle}
/>
</Form.Field>

View file

@ -2,7 +2,7 @@ import React from 'react';
import { useObserver } from 'mobx-react-lite';
import DashboardMetricSelection from '../DashboardMetricSelection';
import DashboardForm from '../DashboardForm';
import { Button } from 'UI';
import { Button } from 'antd';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { useStore } from 'App/mstore';
import { useModal } from 'App/components/Modal';
@ -67,7 +67,7 @@ function DashboardModal(props: Props) {
<div className="flex items-center absolute bottom-0 left-0 right-0 bg-white border-t p-3">
<Button
variant="primary"
type="primary"
disabled={!dashboard.isValid || loading}
onClick={onSave}
className="flaot-left mr-2"

View file

@ -1,83 +0,0 @@
import {useObserver} from 'mobx-react-lite';
import React from 'react';
import {Button, Modal, Form, Icon} from 'UI';
import {useStore} from 'App/mstore'
import Select from 'Shared/Select';
interface Props {
metricId: string,
show: boolean;
closeHandler?: () => void;
}
function DashboardSelectionModal(props: Props) {
const {show, metricId, closeHandler} = props;
const {dashboardStore} = useStore();
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
key: i.id,
label: i.name,
value: i.dashboardId,
}));
const [selectedId, setSelectedId] = React.useState(dashboardOptions[0].value);
const onSave = () => {
const dashboard = dashboardStore.getDashboard(selectedId)
if (dashboard) {
dashboardStore.addWidgetToDashboard(dashboard, [metricId]).then(closeHandler)
}
}
React.useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape' || e.key === 'Esc') {
closeHandler();
}
}
document.addEventListener('keydown', handleEsc, false);
return () => {
document.removeEventListener('keydown', handleEsc, false);
}
}, [])
return useObserver(() => (
<Modal size="small" open={show} onClose={closeHandler}>
<Modal.Header className="flex items-center justify-between">
<div className='text-xl font-medium'>{'Add to selected dashboard'}</div>
<Icon
role="button"
tabIndex="-1"
color="gray-dark"
size="14"
name="close"
onClick={closeHandler}
/>
</Modal.Header>
<Modal.Content>
<Form.Field>
<label className="mb-2">{'Dashbaord:'}</label>
<Select
options={dashboardOptions}
defaultValue={dashboardOptions[0].value}
onChange={({value}: any) => setSelectedId(value.value)}
/>
</Form.Field>
</Modal.Content>
<Modal.Footer>
<Button
variant="primary"
onClick={onSave}
className="float-left mr-2 "
>
Add
</Button>
<Button className="mr-2" onClick={closeHandler}>{'Cancel'}</Button>
</Modal.Footer>
</Modal>
));
}
export default DashboardSelectionModal;

View file

@ -1,6 +1,7 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { Button, Loader } from 'UI';
import { Loader } from 'UI';
import { Button } from 'antd'
import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper';
import { useStore } from 'App/mstore';
import { useModal } from 'App/components/Modal';
@ -63,7 +64,7 @@ function AddMetric({ history, siteId, title, description }: IProps) {
<div className="text-disabled-text">{description}</div>
</div>
<Button variant="text-primary" className="font-medium ml-2" onClick={onCreateNew}>
<Button variant="text" className="text-main font-medium ml-2" onClick={onCreateNew}>
+ Create New
</Button>
</div>
@ -100,7 +101,7 @@ function AddMetric({ history, siteId, title, description }: IProps) {
{' out of '}
<span className="font-medium">{metrics ? metrics.length : 0}</span>
</div>
<Button variant="primary" disabled={selectedWidgetIds.length === 0} onClick={onSave}>
<Button type="primary" disabled={selectedWidgetIds.length === 0} onClick={onSave}>
Add Selected
</Button>
</div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { Button, Loader } from 'UI';
import { Loader } from 'UI';
import { Button } from 'antd'
import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper';
import { useStore } from 'App/mstore';
import { useModal } from 'App/components/Modal';
@ -79,7 +80,7 @@ function AddPredefinedMetric({ history, siteId, title, description }: IProps) {
</div>
<div className="flex flex-col items-end">
<Button variant="text-primary" className="font-medium ml-2" onClick={onCreateNew}>
<Button type="text" className="text-main font-medium ml-2" onClick={onCreateNew}>
+ Create Custom Metric
</Button>
<div className="text-disabled-text">Past 7 Days</div>
@ -145,7 +146,7 @@ function AddPredefinedMetric({ history, siteId, title, description }: IProps) {
{' out of '}
<span className="font-medium">{totalMetricCount}</span>
</div>
<Button variant="primary" disabled={selectedWidgetIds.length === 0} onClick={onSave}>
<Button type="primary" disabled={selectedWidgetIds.length === 0} onClick={onSave}>
Add Selected
</Button>
</div>

View file

@ -2,7 +2,7 @@ import { useModal } from 'App/components/Modal';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import React, { useMemo } from 'react';
import { Button } from 'UI';
import { Button } from 'antd';
function FooterContent({ dashboardId, selected }: any) {
const { hideModal } = useModal();
@ -27,10 +27,10 @@ function FooterContent({ dashboardId, selected }: any) {
<span className="font-medium">{total}</span>
</div>
<div className="flex items-center">
<Button variant="text-primary" className="mr-2" onClick={hideModal}>
<Button type="text" className="mr-2" onClick={hideModal}>
Cancel
</Button>
<Button disabled={selected.length === 0} variant="primary" onClick={addSelectedToDashboard}>
<Button disabled={selected.length === 0} type="primary" onClick={addSelectedToDashboard}>
Add Selected to Dashboard
</Button>
</div>

View file

@ -65,7 +65,7 @@ function WidgetChart(props: Props) {
const prevMetricRef = useRef<any>();
const isMounted = useIsMounted();
const [compData, setCompData] = useState<any>(null);
const [enabledRows, setEnabledRows] = useState<string[]>([]);
const [enabledRows, setEnabledRows] = useState<string[]>(_metric.series.map(s => s.name));
const isTableWidget =
_metric.metricType === 'table' && _metric.viewType === 'table';
const isPieChart =
@ -78,6 +78,17 @@ function WidgetChart(props: Props) {
};
}, []);
useEffect(() => {
if (enabledRows.length !== _metric.series.length) {
const excluded = _metric.series
.filter((s) => !enabledRows.includes(s.name))
.map((s) => s.name);
metricStore.setDisabledSeries(excluded);
} else {
metricStore.setDisabledSeries([]);
}
}, [enabledRows])
useEffect(() => {
if (!data.chart) return;
const series = data.chart[0]
@ -227,7 +238,7 @@ function WidgetChart(props: Props) {
]);
useEffect(loadPage, [_metric.page]);
const onFocus = (seriesName: string)=> {
const onFocus = (seriesName: string) => {
metricStore.setFocusedSeriesName(seriesName);
metricStore.setDrillDown(true)
}
@ -318,6 +329,7 @@ function WidgetChart(props: Props) {
inGrid={!props.isPreview}
data={chartData}
compData={compDataCopy}
onSeriesFocus={onFocus}
onClick={onChartClick}
label={
_metric.metricOf === 'sessionCount'
@ -335,6 +347,7 @@ function WidgetChart(props: Props) {
data={chartData}
inGrid={!props.isPreview}
onClick={onChartClick}
onSeriesFocus={onFocus}
label={
_metric.metricOf === 'sessionCount'
? 'Number of Sessions'
@ -351,6 +364,7 @@ function WidgetChart(props: Props) {
compData={compDataCopy}
params={params}
colors={colors}
onSeriesFocus={onFocus}
onClick={onChartClick}
label={
_metric.metricOf === 'sessionCount'
@ -538,6 +552,7 @@ function WidgetChart(props: Props) {
const showTable = _metric.metricType === TIMESERIES && (props.isPreview || _metric.viewType === TABLE)
const tableMode = _metric.viewType === 'table' && _metric.metricType === TIMESERIES
return (
<div ref={ref}>
{loading ? stale ? <LongLoader onClick={loadSample} /> : <Loader loading={loading} style={{ height: `240px` }} /> : (
@ -549,6 +564,7 @@ function WidgetChart(props: Props) {
inBuilder={props.isPreview}
defaultOpen={true}
data={data}
tableMode={tableMode}
enabledRows={enabledRows}
setEnabledRows={setEnabledRows}
metric={_metric}

View file

@ -31,6 +31,7 @@ interface Props {
defaultOpen?: boolean;
metric: { name: string; viewType: string };
inBuilder?: boolean;
tableMode?: boolean;
}
function WidgetDatatable(props: Props) {
@ -42,6 +43,7 @@ function WidgetDatatable(props: Props) {
dataObj.chart = dataObj.chart.map((item, i) => {
const compItem = props.compData!.chart[i];
const newItem = { ...item };
if (!compItem) return newItem;
Object.keys(compItem).forEach((key) => {
if (key !== 'timestamp' && key !== 'time') {
newItem[key] = compItem[key];
@ -161,7 +163,7 @@ function WidgetDatatable(props: Props) {
columns={tableProps}
dataSource={tableData}
pagination={false}
rowSelection={rowSelection}
rowSelection={props.tableMode ? undefined : rowSelection}
size={'small'}
scroll={{ x: 'max-content' }}
/>

View file

@ -1,15 +1,13 @@
import React, {useEffect, useState} from 'react';
import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptions';
import { metricOf } from 'App/constants/filterOptions';
import {FilterKey} from 'Types/filter/filterType';
import {useStore} from 'App/mstore';
import {observer} from 'mobx-react-lite';
import {Button, Icon, confirm, Tooltip} from 'UI';
import {Input, Alert} from 'antd'
import { Icon, confirm, Tooltip } from 'UI';
import { Input, Alert, Button } from 'antd'
import FilterSeries from '../FilterSeries';
import Select from 'Shared/Select';
import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes';
import MetricTypeDropdown from './components/MetricTypeDropdown';
import MetricSubtypeDropdown from './components/MetricSubtypeDropdown';
import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes';
import {
TIMESERIES,
TABLE,
@ -285,8 +283,8 @@ function WidgetForm(props: Props) {
{`${isTable || isFunnel || isClickmap || isInsights || isPathAnalysis || isRetention ? 'Filter by' : 'Chart Series'}`}
{!isTable && !isFunnel && !isClickmap && !isInsights && !isPathAnalysis && !isRetention && (
<Button
className='ml-2'
variant='text-primary'
className='ml-2 text-main'
type='text'
onClick={() => metric.addSeries()}
disabled={!canAddSeries}
>
@ -327,7 +325,7 @@ function WidgetForm(props: Props) {
disabled={!cannotSaveFunnel}
>
<div className='flex items-center'>
<Button variant='primary' onClick={onSave} disabled={isSaving || cannotSaveFunnel}>
<Button type='primary' onClick={onSave} disabled={isSaving || cannotSaveFunnel}>
{metric.exists()
? 'Update'
: parseInt(dashboardId) > 0
@ -335,7 +333,7 @@ function WidgetForm(props: Props) {
: 'Create'}
</Button>
{metric.exists() && metric.hasChanged && (
<Button onClick={undoChanges} variant='text' icon='arrow-counterclockwise' className='ml-2'>
<Button onClick={undoChanges} type='text' icon={<Icon name={'arrow-counterclockwise'} />} className='ml-2'>
Undo
</Button>
)}
@ -343,8 +341,7 @@ function WidgetForm(props: Props) {
</Tooltip>
<div className='flex items-center'>
{metric.exists() && (
<Button variant='text-primary' onClick={onDelete}>
<Icon name='trash' size='14' className='mr-2' color='teal'/>
<Button type='text' className={'text-main'} icon={<Icon name='trash' size='14' className='mr-2' color='teal'/>} onClick={onDelete}>
Delete
</Button>
)}

View file

@ -55,7 +55,7 @@ function WidgetName(props: Props) {
onBlur={() => onBlur()}
onKeyDown={onKeyDown}
onFocus={() => setEditing(true)}
maxLength={80}
maxLength={40}
className="bg-white text-2xl ps-2 rounded-lg h-8"
/>
) : (

View file

@ -11,7 +11,7 @@ import { debounce } from 'App/utils';
import useIsMounted from 'App/hooks/useIsMounted';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { numberWithCommas } from 'App/utils';
import { HEATMAP, USER_PATH } from "App/constants/card";
import { HEATMAP, USER_PATH, FUNNEL } from "App/constants/card";
interface Props {
className?: string;
@ -59,6 +59,13 @@ function WidgetSessions(props: Props) {
if (!isMounted()) return;
setLoading(true);
delete filter.eventsOrderSupport;
if (widget.metricType === FUNNEL) {
if (filter.series[0].filter.filters.length === 0) {
setLoading(false);
return setData([]);
}
}
widget
.fetchSessions(metricId, filter)
.then((res: any) => {
@ -108,11 +115,24 @@ function WidgetSessions(props: Props) {
debounceClickMapSearch(customFilter);
} else {
const hasStartPoint = !!widget.startPoint && widget.metricType === USER_PATH
const activeSeries = focusedSeries ? widget.series.filter((s) => s.name === focusedSeries) : widget.series
const onlyFocused = focusedSeries
? widget.series.filter((s) => s.name === focusedSeries)
: widget.series
const activeSeries = metricStore.disabledSeries.length
? onlyFocused.filter((s) => !metricStore.disabledSeries.includes(s.name))
: onlyFocused
const seriesJson = activeSeries.map((s) => s.toJson());
if (hasStartPoint) {
seriesJson[0].filter.filters.push(widget.startPoint.toJson());
}
if (widget.metricType === USER_PATH) {
if (seriesJson[0].filter.filters[0].value[0] === '' && widget.data.nodes) {
seriesJson[0].filter.filters[0].value = widget.data.nodes[0].name
} else if (seriesJson[0].filter.filters[0].value[0] === '' && !widget.data.nodes) {
// no point requesting if we don't have starting point picked by api
return;
}
}
debounceRequest(widget.metricId, {
...filter,
series: seriesJson,
@ -132,6 +152,8 @@ function WidgetSessions(props: Props) {
metricStore.clickMapSearch,
focusedSeries,
widget.startPoint,
widget.data.nodes,
metricStore.disabledSeries.length
]);
useEffect(loadData, [metricStore.sessionsPage]);
useEffect(() => {

View file

@ -11,7 +11,8 @@ import { sessions as sessionsRoute } from 'App/routes';
import Divider from 'Components/Errors/ui/Divider';
import ErrorName from 'Components/Errors/ui/ErrorName';
import Label from 'Components/Errors/ui/Label';
import { Button, ErrorDetails, Icon, Loader } from 'UI';
import { ErrorDetails, Icon, Loader } from 'UI';
import { Button } from 'antd'
import SessionBar from './SessionBar';
@ -81,11 +82,12 @@ function MainSection(props) {
</span>
<Button
className="ml-auto"
variant="text-primary"
variant="text"
onClick={findSessions}
icon={<Icon className="ml-1" name="next1" color="teal" />}
iconPosition={'end'}
>
Find all sessions with this error
<Icon className="ml-1" name="next1" color="teal" />
</Button>
</div>
<SessionBar className="my-4" session={error.lastHydratedSession} />

View file

@ -1,11 +1,12 @@
import React from 'react';
import FeatureFlag from 'App/mstore/types/FeatureFlag';
import { Icon, Toggler, Link, TextEllipsis, Tooltip } from 'UI';
import { Icon, Link, TextEllipsis, Tooltip } from 'UI';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { resentOrDate } from 'App/date';
import { toast } from 'react-toastify';
import { fflagRead } from "App/routes";
import { Switch } from 'antd';
function FFlagItem({ flag }: { flag: FeatureFlag }) {
const { featureFlagsStore, userStore } = useStore();
@ -35,7 +36,10 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
<div className={'flex items-center'}>
<Link style={{ flex: 1 }} to={fflagRead(flag.featureFlagId.toString())}>
<div className={'flex items-center gap-2'}>
<Tooltip delay={150} title={flag.isSingleOption ? 'Single variant' : 'Multivariant'}>
<Tooltip
delay={150}
title={flag.isSingleOption ? 'Single variant' : 'Multivariant'}
>
<Icon name={flagIcon} size={32} />
</Tooltip>
<div className="flex flex-col gap-1" style={{ width: 300 }}>
@ -50,18 +54,27 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
</div>
</div>
</Link>
<div style={{ flex: 1 }}>{resentOrDate(flag.updatedAt || flag.createdAt)}</div>
<div style={{ flex: 1 }} className={'flex items-center gap-2 capitalize'}>
<div style={{ flex: 1 }}>
{resentOrDate(flag.updatedAt || flag.createdAt)}
</div>
<div
style={{ flex: 1 }}
className={'flex items-center gap-2 capitalize'}
>
<Icon name={'person-fill'} />
{user}
</div>
<div style={{ marginLeft: 'auto', width: 115 }}>
<Toggler
checked={flag.isActive}
name={'persist-flag'}
label={flag.isActive ? 'Enabled' : 'Disabled'}
onChange={toggleActivity}
/>
<div
style={{
marginLeft: 'auto',
width: 115,
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
}}
>
<Switch checked={flag.isActive} onChange={toggleActivity} />
<div>{flag.isActive ? 'Enabled' : 'Disabled'}</div>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
import React from 'react'
import { Button, PageTitle } from 'UI'
import { PageTitle } from 'UI'
import { Button } from 'antd'
import FFlagsSearch from "Components/FFlags/FFlagsSearch";
import { useHistory } from "react-router";
import { newFFlag, withSiteId } from 'App/routes';
@ -13,7 +14,7 @@ function FFlagsListHeader({ siteId }: { siteId: string }) {
<PageTitle title="Feature Flags" />
</div>
<div className="ml-auto flex items-center">
<Button variant="primary" onClick={() => history.push(withSiteId(newFFlag(), siteId))}>
<Button type="primary" onClick={() => history.push(withSiteId(newFFlag(), siteId))}>
Create Feature Flag
</Button>
<div className="mx-2"></div>

View file

@ -1,7 +1,8 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Toggler, Loader, Button, NoContent, ItemMenu } from 'UI';
import { Loader, NoContent, ItemMenu } from 'UI';
import { Button, Switch } from 'antd'
import Breadcrumb from 'Shared/Breadcrumb';
import { useHistory } from 'react-router';
import { withSiteId, fflag, fflags } from 'App/routes';
@ -59,11 +60,16 @@ function FlagView({ siteId, fflagId }: { siteId: string; fflagId: string }) {
<div className={'flex items-center gap-2'}>
<div className={'text-2xl'}>{current.flagKey}</div>
<Button
className={'ml-auto'}
variant={'text-primary'}
className={'ml-auto text-main'}
type={'text'}
onClick={() =>
history.push(
withSiteId(fflag(featureFlagsStore.currentFflag?.featureFlagId.toString()), siteId)
withSiteId(
fflag(
featureFlagsStore.currentFflag?.featureFlagId.toString()
),
siteId
)
)
}
>
@ -72,17 +78,16 @@ function FlagView({ siteId, fflagId }: { siteId: string; fflagId: string }) {
<ItemMenu bold items={menuItems} />
</div>
<div className={'border-b'} style={{ color: 'rgba(0,0,0, 0.6)' }}>
{current.description || 'There is no description for this feature flag.'}
{current.description ||
'There is no description for this feature flag.'}
</div>
<div className={'mt-4'}>
<label className={'font-semibold'}>Status</label>
<Toggler
checked={current.isActive}
name={'persist-flag'}
onChange={toggleActivity}
label={current.isActive ? 'Enabled' : 'Disabled'}
/>
<div className={'flex items-center gap-2'}>
<Switch checked={current.isActive} onChange={toggleActivity} />
<div>{current.isActive ? 'Enabled' : 'Disabled'}</div>
</div>
</div>
<div className={'mt-4'}>
<label className={'font-semibold'}>Persistence</label>
@ -92,28 +97,25 @@ function FlagView({ siteId, fflagId }: { siteId: string; fflagId: string }) {
: 'This flag is not persistent across authentication events.'}
</div>
</div>
{!current.isSingleOption ? (
<Multivariant readonly />
{!current.isSingleOption ? <Multivariant readonly /> : null}
{current.conditions.length > 0 ? (
<div className="mt-6 p-4 rounded bg-gray-lightest">
<label className={'font-semibold'}>Rollout Conditions</label>
{current.conditions.map((condition, index) => (
<React.Fragment key={index}>
<RolloutCondition
set={index + 1}
readonly
index={index}
conditions={condition}
removeCondition={current.removeCondition}
/>
<div className={'mt-2'} />
</React.Fragment>
))}
</div>
) : null}
{current.conditions.length > 0 ? (
<div className="mt-6 p-4 rounded bg-gray-lightest">
<label className={'font-semibold'}>Rollout Conditions</label>
{current.conditions.map((condition, index) => (
<React.Fragment key={index}>
<RolloutCondition
set={index + 1}
readonly
index={index}
conditions={condition}
removeCondition={current.removeCondition}
/>
<div className={'mt-2'} />
</React.Fragment>
))}
</div>
) : null}
</div>
</div>
);
}

View file

@ -1,6 +1,7 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { Button } from 'UI';
import { Icon } from 'UI';
import { Button } from 'antd';
import cn from 'classnames';
import FeatureFlag from 'App/mstore/types/FeatureFlag';
@ -46,8 +47,8 @@ function Description({
</div>
) : (
<Button
variant={'text-primary'}
icon={'edit'}
type={'text'}
icon={<Icon name={'edit'} size={16} />}
onClick={() => setEditing({ isDescrEditing: true })}
>
Add

View file

@ -1,5 +1,4 @@
import React from 'react';
import { Button } from 'UI';
import { observer } from 'mobx-react-lite';
import cn from 'classnames';
import { ItemMenu } from 'UI';
@ -7,6 +6,7 @@ import { useStore } from 'App/mstore';
import { useHistory } from 'react-router';
import { toast } from 'react-toastify';
import { fflags, withSiteId } from "App/routes";
import { Button } from 'antd';
function Header({ current, onCancel, onSave, isNew, siteId }: any) {
const { featureFlagsStore } = useStore();
@ -29,10 +29,10 @@ function Header({ current, onCancel, onSave, isNew, siteId }: any) {
</div>
<div className={'flex items-center gap-2'}>
<Button variant="text-primary" onClick={onCancel}>
<Button type="text" onClick={onCancel}>
Cancel
</Button>
<Button variant="primary" onClick={onSave}>
<Button type="primary" onClick={onSave}>
Save
</Button>
{!isNew ? <ItemMenu bold items={menuItems} /> : null}

View file

@ -1,6 +1,7 @@
import React from 'react';
import { Rollout, Payload } from './Helpers';
import { Input, Button, Icon } from 'UI';
import { Input, Icon } from 'UI';
import { Button } from 'antd'
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import cn from 'classnames';
@ -164,7 +165,7 @@ function Multivariant({ readonly }: { readonly?: boolean }) {
{readonly ? null : (
<div className={'mt-2 flex justify-between w-full'}>
{featureFlagsStore.currentFflag!.variants.length < 10 ? (
<Button variant={'text-primary'} onClick={featureFlagsStore.currentFflag!.addVariant}>
<Button type={'text'} onClick={featureFlagsStore.currentFflag!.addVariant}>
+ Add Variant
</Button>
) : null}

View file

@ -1,8 +1,9 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Input, SegmentSelection, Toggler, Loader, Button, NoContent } from 'UI';
import { Input, SegmentSelection, Loader, NoContent } from 'UI';
import Breadcrumb from 'Shared/Breadcrumb';
import { Button, Switch } from 'antd'
import { useModal } from 'App/components/Modal';
import HowTo from 'Components/FFlags/NewFFlag/HowTo';
import {Prompt, useHistory} from 'react-router';
@ -92,7 +93,13 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
/>
<div className={'w-full bg-white rounded p-4 widget-wrapper'}>
<div className="flex justify-between items-center">
<Header siteId={siteId} current={current} onCancel={onCancel} onSave={onSave} isNew={!fflagId} />
<Header
siteId={siteId}
current={current}
onCancel={onCancel}
onSave={onSave}
isNew={!fflagId}
/>
</div>
<div className={'w-full border-b border-light-gray my-2'} />
@ -106,7 +113,9 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
current.setFlagKey(e.target.value.replace(/\s/g, '-'));
}}
/>
<div className={'text-sm text-disabled-text mt-1 flex items-center gap-1'}>
<div
className={'text-sm text-disabled-text mt-1 flex items-center gap-1'}
>
Feature flag keys must be unique.
<div className={'link'} onClick={onImplementClick}>
Learn how to implement feature flags
@ -142,10 +151,16 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
</div>
{current.isSingleOption ? (
<>
<div className={'text-sm text-disabled-text mt-1 flex items-center gap-1'}>
<div
className={
'text-sm text-disabled-text mt-1 flex items-center gap-1'
}
>
Users will be served
<code className={'p-1 text-red rounded bg-gray-lightest'}>true</code> if they match
one or more rollout conditions.
<code className={'p-1 text-red rounded bg-gray-lightest'}>
true
</code>{' '}
if they match one or more rollout conditions.
</div>
<div className={'mt-6'}>
<Payload />
@ -156,7 +171,6 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
}}
placeholder={"E.g. red button, {'buttonColor': 'red'}"}
className={'mt-2'}
/>
</div>
</>
@ -166,50 +180,65 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
</div>
<div className={'mt-6'}>
<label className={'font-semibold'}>Persist flag across authentication</label>
<Toggler
checked={current.isPersist}
name={'persist-flag'}
onChange={() => {
current.setIsPersist(!current.isPersist);
}}
label={current.isPersist ? 'Yes' : 'No'}
/>
<label className={'font-semibold'}>
Persist flag across authentication
</label>
<div className="flex items-center gap-2">
<Switch
checked={current.isPersist}
onChange={() => {
current.setIsPersist(!current.isPersist);
}}
/>
<div>{current.isPersist ? 'Yes' : 'No'}</div>
</div>
<div className={'text-sm text-disabled-text flex items-center gap-1'}>
Persist flag to not reset this feature flag status after a user is identified.
Persist flag to not reset this feature flag status after a user is
identified.
</div>
</div>
<div className={'mt-6'}>
<label className={'font-semibold'}>Enable this feature flag (Status)?</label>
<Toggler
checked={current.isActive}
name={'persist-flag'}
onChange={() => {
!fflagId && !current.isActive ? toast.success("Feature flag will be enabled upon saving it.") : ""
current.setIsEnabled(!current.isActive);
}}
label={current.isActive ? 'Enabled' : 'Disabled'}
/>
<label className={'font-semibold'}>
Enable this feature flag (Status)?
</label>
<div className="flex items-center gap-2">
<Switch
checked={current.isActive}
onChange={() => {
!fflagId && !current.isActive
? toast.success(
'Feature flag will be enabled upon saving it.'
)
: '';
current.setIsEnabled(!current.isActive);
}}
/>
<div>{current.isActive ? 'Enabled' : 'Disabled'}</div>
</div>
</div>
<div className={'mt-6 p-4 rounded bg-gray-lightest'}>
<label className={'font-semibold'}>Rollout Conditions</label>
{current.conditions.length === 0 ? null : (
<div className={'text-sm text-disabled-text mb-2'}>
Indicate the users for whom you intend to make this flag available. Keep in mind that
each set of conditions will be deployed separately from one another.
Indicate the users for whom you intend to make this flag
available. Keep in mind that each set of conditions will be
deployed separately from one another.
</div>
)}
<NoContent
show={current.conditions.length === 0}
title={'The flag will be available for 100% of the user sessions.'}
subtext={
<div className={'flex flex-col items-center'} style={{ fontSize: 14 }}>
<div
className={'flex flex-col items-center'}
style={{ fontSize: 14 }}
>
<div className={'text-sm mb-1'}>
Set up condition sets to restrict the rollout.
</div>
<Button onClick={() => current!.addCondition()} variant={'text-primary'}>
<Button onClick={() => current!.addCondition()} type={'text'}>
+ Create Condition Set
</Button>
</div>
@ -237,7 +266,7 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
'flex items-center justify-center w-full bg-white rounded border mt-2 p-2'
}
>
<Button variant={'text-primary'}>+ Create Condition Set</Button>
<Button type={'text'}>+ Create Condition Set</Button>
</div>
) : null}
</>

View file

@ -2,7 +2,8 @@ import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import ReCAPTCHA from 'react-google-recaptcha';
import { Form, Input, Loader, Button, Icon, Message } from 'UI';
import { Form, Input, Loader, Icon, Message } from 'UI';
import { Button } from 'antd'
import stl from './forgotPassword.module.css';
import { validatePassword } from 'App/validate';
import { PASSWORD_POLICY } from 'App/constants';
@ -112,7 +113,10 @@ function CreatePassword(props: Props) {
</div>
</Loader>
<div className="mt-4">
<div data-hidden={!updated} className="flex items-center flex-col text-center">
<div
data-hidden={!updated}
className="flex items-center flex-col text-center"
>
<div className="w-10 h-10 bg-tealx-lightest rounded-full flex items-center justify-center mb-3">
<Icon name="check" size="30" color="tealx" />
</div>
@ -122,9 +126,16 @@ function CreatePassword(props: Props) {
{validationError && <Message error>{validationError}</Message>}
<Button type="submit" data-hidden={updated} variant="primary" loading={loading} className="w-full mt-4">
Create
</Button>
{updated ? null :
<Button
htmlType="submit"
type="primary"
loading={loading}
className="w-full mt-4"
>
Create
</Button>
}
</>
)}

View file

@ -1,15 +0,0 @@
import React from 'react';
import { withRouter } from 'react-router-dom';
interface Props {
}
function FunnelDetails(props: Props) {
return (
<div>
Create View/Detail View
</div>
);
}
export default withRouter(FunnelDetails);

View file

@ -1 +0,0 @@
export { default } from './FunnelDetails'

View file

@ -1 +0,0 @@
//export { default } from './FunnelIssueDetails'

View file

@ -1,61 +0,0 @@
import { PageTitle, Button, Pagination, Icon, Loader } from 'UI';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { sliceListPerPage } from 'App/utils';
import FunnelItem from '../FunnelItem/FunnelItem';
import FunnelSearch from '../FunnelSearch';
function FunnelList(props) {
const { funnelStore } = useStore()
const list = useObserver(() => funnelStore.list)
const loading = useObserver(() => funnelStore.isLoading)
useEffect(() => {
if (list.length === 0) {
funnelStore.fetchFunnels()
}
}, [])
return (
<Loader loading={loading}>
<div className="flex items-center">
<div className="flex items-center">
<PageTitle title='Funnels' className="mr-3" />
<Button primary size="small" onClick={() => {}}>New Funnel</Button>
</div>
<div className="ml-auto w-1/4">
<FunnelSearch />
</div>
</div>
<div className="color-gray-medium mt-2 mb-4">Funnels make it easy to uncover the most significant issues that impacted conversions.</div>
<div className="mt-3 border rounded bg-white">
<div className="grid grid-cols-12 p-3 font-medium">
<div className="col-span-4 flex items-center">
<Icon name="funnel-fill"/> <span className="ml-2">Title</span>
</div>
<div className="col-span-3">Owner</div>
<div className="col-span-3">Last Modified</div>
</div>
{sliceListPerPage(list, funnelStore.page - 1, funnelStore.pageSize).map((funnel: any) => (
<FunnelItem funnel={funnel} />
))}
</div>
<div className="w-full flex items-center justify-center py-8">
<Pagination
page={funnelStore.page}
total={list.length}
onPageChange={(page) => funnelStore.updateKey('page', page)}
limit={funnelStore.pageSize}
debounceRequest={100}
/>
</div>
</Loader>
);
}
export default FunnelList;

View file

@ -1 +0,0 @@
export { default } from './FunnelList';

View file

@ -1,26 +0,0 @@
import React from 'react';
import { Switch, Route } from 'react-router';
import FunnelDetails from '../FunnelDetails/FunnelDetails';
import FunnelList from '../FunnelList';
function FunnelPage(props) {
return (
<div className="page-margin container-70">
<Switch>
<Route path="/">
<FunnelList />
</Route>
<Route path="/funnel/create">
<FunnelDetails />
</Route>
{/* <Route path="/funnel/:id">
<FunnelDetail />
</Route> */}
</Switch>
</div>
);
}
export default FunnelPage;

View file

@ -1 +0,0 @@
export { default } from './FunnelPage';

View file

@ -4,7 +4,7 @@ import type { TableProps } from 'antd';
import Widget from 'App/mstore/types/widget';
import Funnel from 'App/mstore/types/funnel';
import { ItemMenu } from 'UI';
import { EllipsisVertical, FileDown } from 'lucide-react';
import { EllipsisVertical } from 'lucide-react';
import { exportAntCsv } from '../../../utils';
interface Props {
@ -14,12 +14,7 @@ interface Props {
}
function FunnelTable(props: Props) {
const tableData = [
{
conversion: props.data.funnel.totalConversionsPercentage,
},
];
const tableProps: TableProps['columns'] = [
const defaultTableProps: TableProps['columns'] = [
{
title: 'Conversion %',
dataIndex: 'conversion',
@ -34,31 +29,43 @@ function FunnelTable(props: Props) {
),
},
];
const defaultData = [
{
conversion: props.data.funnel.totalConversionsPercentage,
},
]
const [tableProps, setTableProps] = React.useState(defaultTableProps);
const [tableData, setTableData] = React.useState(defaultData);
React.useEffect(() => {
const funnel = props.data.funnel;
const funnel = props.data.funnel
const tablePropsCopy = defaultTableProps;
const tableDataCopy = defaultData;
funnel.stages.forEach((st, ind) => {
const title = `${st.label} ${st.operator} ${st.value.join(' or ')}`;
const wrappedTitle =
title.length > 40 ? title.slice(0, 40) + '...' : title;
tableProps.push({
tablePropsCopy.push({
title: wrappedTitle,
dataIndex: 'st_' + ind,
key: 'st_' + ind,
ellipsis: true,
width: 120,
});
tableData[0]['st_' + ind] = st.count;
tableDataCopy[0]['st_' + ind] = st.count;
});
if (props.compData) {
tableData.push({
tableDataCopy.push({
conversion: props.compData.funnel.totalConversionsPercentage,
})
const compFunnel = props.compData.funnel;
compFunnel.stages.forEach((st, ind) => {
tableData[1]['st_' + ind] = st.count;
tableDataCopy[1]['st_' + ind] = st.count;
});
}
setTableProps(tablePropsCopy);
setTableData(tableDataCopy);
}, [props.data]);
return (

View file

@ -87,18 +87,26 @@ function FunnelWidget(props: Props) {
const viewType = metric?.viewType;
const isHorizontal = viewType === 'columnChart';
const noEvents = metric.series[0].filter.filters.length === 0;
return (
<NoContent
style={{ minHeight: 220 }}
title={
<div className="flex items-center text-lg">
<Icon name="info-circle" className="mr-2" size="18" />
No data available for the selected period.
{noEvents ? 'Select an event to start seeing the funnel' : 'No data available for the selected period.'}
</div>
}
show={!stages || stages.length === 0}
>
<div className={cn('w-full border-b -mx-4 px-4', isHorizontal ? 'overflow-x-scroll custom-scrollbar flex gap-2 justify-around' : '')}>
<div
className={cn(
'w-full border-b -mx-4 px-4',
isHorizontal
? 'overflow-x-scroll custom-scrollbar flex gap-2 justify-around'
: ''
)}
>
{!isWidget &&
shownStages.map((stage: any, index: any) => (
<Stage

View file

@ -1,7 +1,8 @@
import React from 'react';
// @ts-ignore
import slide from 'App/svg/cheers.svg';
import { Button, Loader } from 'UI';
import { Loader } from 'UI';
import { Button } from 'antd'
import Footer from './Footer';
import { getHighest } from 'App/constants/zindex';
import Category from 'Components/Header/HealthStatus/ServiceCategory';
@ -81,7 +82,8 @@ function HealthModal({
disabled={isLoading}
onClick={getHealth}
icon={'arrow-repeat'}
variant={'text-primary'}
type={'text'}
className={'text-main'}
>
Recheck
</Button>
@ -132,7 +134,7 @@ function HealthModal({
<Button
disabled={!healthResponse?.overallHealth}
loading={isLoading}
variant={'primary'}
type={'primary'}
className={'ml-auto'}
onClick={() => setPassed?.()}
>

View file

@ -2,12 +2,12 @@ import { Segmented } from 'antd';
import React from 'react';
import CircleNumber from '../CircleNumber';
import MetadataList from '../MetadataList/MetadataList';
import { HighlightCode, Icon, Button } from 'UI';
import { HighlightCode, Icon } from 'UI';
import DocCard from 'Shared/DocCard/DocCard';
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
import { OB_TABS } from 'App/routes';
import withPageTitle from 'App/components/hocs/withPageTitle';
import { Button as AntButton } from 'antd'
import { Button } from 'antd'
interface Props extends WithOnboardingProps {
platforms: Array<{
@ -42,10 +42,10 @@ function IdentifyUsersTab(props: Props) {
href={`https://docs.openreplay.com/en/installation/identify-user${platform.value === "web" ? "/#with-npm" : "/#with-ios-app"}`}
target="_blank"
>
<AntButton size={'small'} type={'text'} className="ml-2 flex items-center gap-2">
<Button size={'small'} type={'text'} className="ml-2 flex items-center gap-2">
<Icon name={'question-circle'} />
<div className={'text-main'}>See Documentation</div>
</AntButton>
</Button>
</a>
</h1>
<div className="p-4 flex gap-2 items-center">
@ -142,11 +142,11 @@ function IdentifyUsersTab(props: Props) {
</div>
<div className="border-t px-4 py-3 flex justify-end gap-4">
<Button variant="text-primary" onClick={() => (props.skip ? props.skip() : null)}>
<Button type="text" onClick={() => (props.skip ? props.skip() : null)}>
Skip
</Button>
<Button
variant="primary"
type="primary"
className=""
onClick={() => (props.navTo ? props.navTo(OB_TABS.MANAGE_USERS) : null)}
>

View file

@ -2,13 +2,13 @@ import React from 'react';
import OnboardingTabs from '../OnboardingTabs';
import MobileOnboardingTabs from '../OnboardingTabs/OnboardingMobileTabs';
import ProjectFormButton from '../ProjectFormButton';
import { Button, Icon } from 'UI';
import { Icon } from 'UI';
import withOnboarding from '../withOnboarding';
import { WithOnboardingProps } from '../withOnboarding';
import { OB_TABS } from 'App/routes';
import withPageTitle from 'App/components/hocs/withPageTitle';
import { Segmented } from 'antd';
import { Button as AntButton } from 'antd'
import { Button } from 'antd'
interface Props extends WithOnboardingProps {
platforms: Array<{
@ -41,10 +41,10 @@ function InstallOpenReplayTab(props: Props) {
</div>
</div>
<a href={"https://docs.openreplay.com/en/using-or/"} target="_blank">
<AntButton size={"small"} type={"text"} className="ml-2 flex items-center gap-2">
<Button size={"small"} type={"text"} className="ml-2 flex items-center gap-2">
<Icon name={"question-circle"} />
<div className={"text-main"}>See Documentation</div>
</AntButton>
</Button>
</a>
</h1>
<div className="p-4 flex gap-2 items-center">
@ -62,7 +62,7 @@ function InstallOpenReplayTab(props: Props) {
</div>
<div className="border-t px-4 py-3 flex justify-end">
<Button
variant="primary"
type="primary"
className=""
onClick={() => (props.navTo ? props.navTo(OB_TABS.IDENTIFY_USERS) : null)}
>

View file

@ -1,9 +1,8 @@
import { Button as AntButton } from 'antd';
import React from 'react';
import { Icon } from 'UI';
import { Button } from 'antd';
import Integrations from 'App/components/Client/Integrations/Integrations';
import withPageTitle from 'App/components/hocs/withPageTitle';
import { Button, Icon } from 'UI';
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
@ -21,21 +20,20 @@ function IntegrationsTab(props: Props) {
href="https://docs.openreplay.com/en/integrations/"
target="_blank"
>
<AntButton
<Button
size={'small'}
type={'text'}
className="ml-2 flex items-center gap-2"
icon={<Icon name={'question-circle'} />}
>
<Icon name={'question-circle'} />
<div className={'text-main'}>See Documentation</div>
</AntButton>
</Button>
</a>
</h1>
<Integrations hideHeader={true} />
<div className="border-t px-4 py-3 flex justify-end">
<Button
variant="primary"
className=""
type="primary"
onClick={() => (props.skip ? props.skip() : null)}
>
Complete Setup

View file

@ -1,11 +1,11 @@
import UsersView from 'App/components/Client/Users/UsersView';
import DocCard from 'Shared/DocCard/DocCard';
import React from 'react';
import { Button, Icon } from 'UI';
import { Icon } from 'UI';
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
import { OB_TABS } from 'App/routes';
import withPageTitle from 'App/components/hocs/withPageTitle';
import { Button as AntButton } from 'antd'
import { Button } from 'antd'
interface Props extends WithOnboardingProps {}
@ -22,10 +22,10 @@ function ManageUsersTab(props: Props) {
href="https://docs.openreplay.com/en/tutorials/adding-users/"
target="_blank"
>
<AntButton size={'small'} type={'text'} className="ml-2 flex items-center gap-2">
<Button size={'small'} type={'text'} className="ml-2 flex items-center gap-2">
<Icon name={'question-circle'} />
<div className={'text-main'}>See Documentation</div>
</AntButton>
</Button>
</a>
</h1>
<div className="grid grid-cols-6 gap-4 p-4">
@ -48,16 +48,16 @@ function ManageUsersTab(props: Props) {
</div>
</div>
<div className="border-t px-4 py-3 flex justify-end">
<Button variant="text-primary" onClick={() => (props.skip ? props.skip() : null)}>
<Button type="text" onClick={() => (props.skip ? props.skip() : null)}>
Skip
</Button>
<Button
variant="primary"
className=""
type="primary"
onClick={() => (props.navTo ? props.navTo(OB_TABS.INTEGRATIONS) : null)}
icon={<Icon name="arrow-right-short" color="white" size={20} />}
iconPosition={'end'}
>
Configure Integrations
<Icon name="arrow-right-short" color="white" size={20} />
</Button>
</div>
</>

View file

@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import { Button, TagBadge } from 'UI';
import { TagBadge, confirm } from 'UI';
import CustomFieldForm from '../../../Client/CustomFields/CustomFieldForm';
import { confirm } from 'UI';
import { useModal } from 'App/components/Modal';
import { toast } from 'react-toastify';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Button } from 'antd'
const MetadataList = () => {
const { customFieldStore, projectsStore } = useStore();
@ -47,7 +47,7 @@ const MetadataList = () => {
return (
<div className="py-2 flex">
<Button variant="outline" onClick={() => openModal()}>
<Button type="default" onClick={() => openModal()}>
Add Metadata
</Button>
<div className="flex ml-2">

View file

@ -1,6 +1,6 @@
import React from 'react'
import { withRouter } from 'react-router'
import { Button } from 'UI'
import { Button } from 'antd'
import { OB_TABS, onboarding as onboardingRoute, withSiteId } from 'App/routes'
import { sessions } from 'App/routes';
import { useStore } from 'App/mstore'
@ -36,7 +36,7 @@ const OnboardingNavButton = ({ match: { params: { activeTab, siteId } }, history
<div className="flex items-center">
<Button
size="small"
variant="outline"
type="outline"
onClick={onDone}
className="float-left mr-2"
>
@ -44,7 +44,7 @@ const OnboardingNavButton = ({ match: { params: { activeTab, siteId } }, history
</Button>
<Button
variant="primary"
type="primary"
onClick={setTab}
>
{BTN_MSGS[activeIndex]}

View file

@ -3,7 +3,7 @@ import stl from './installDocs.module.css';
import cn from 'classnames';
import CircleNumber from '../../CircleNumber';
import { CopyButton, CodeBlock } from 'UI';
import { Toggler } from 'UI';
import { Switch } from 'antd'
const installationCommand = 'npm i @openreplay/tracker';
const usageCode = `import Tracker from '@openreplay/tracker';
@ -57,11 +57,10 @@ function InstallDocs({ site }) {
<div className="mr-2" onClick={() => setIsSpa(!isSpa)}>
Server-Side-Rendered (SSR)?
</div>
<Toggler
<Switch
checked={!isSpa}
name="sessionsLive"
onChange={() => setIsSpa(!isSpa)}
// style={{ lineHeight: '23px' }}
/>
</div>

View file

@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Checkbox, Loader, Toggler } from 'UI';
import GDPR from 'App/mstore/types/gdpr';
import { Checkbox, Loader } from 'UI';
import { Switch } from 'antd'
import cn from 'classnames';
import stl from './projectCodeSnippet.module.css';
import CircleNumber from '../../CircleNumber';
@ -119,13 +119,14 @@ const ProjectCodeSnippet = () => {
instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen
sharing software.
</p>
<Toggler
label="Yes"
checked={isAssistEnabled}
name="test"
className="font-medium mr-2"
onChange={() => setAssistEnabled(!isAssistEnabled)}
/>
<div className={'flex items-center gap-2'}>
<Switch
checked={isAssistEnabled}
className="font-medium mr-2"
onChange={() => setAssistEnabled(!isAssistEnabled)}
/>
<span>Yes</span>
</div>
</div>
<div className={cn(stl.instructions, 'mt-8')}>

View file

@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react';
import cn from 'classnames';
import {observer} from 'mobx-react-lite';
import {Button} from 'UI';
import { Button } from 'antd'
import stl from './AutoplayTimer.module.css';
import clsOv from './overlay.module.css';
import AutoplayToggle from 'Shared/AutoplayToggle';
@ -11,7 +11,7 @@ function AutoplayTimer({history}: any) {
let timer: NodeJS.Timer;
const [cancelled, setCancelled] = useState(false);
const [counter, setCounter] = useState(5);
const {clipStore} = useStore();
const { clipStore } = useStore();
useEffect(() => {
if (counter > 0) {
@ -46,11 +46,11 @@ function AutoplayTimer({history}: any) {
<AutoplayToggle/>
</div>
<div className="flex items-center">
<Button variant="text-primary" onClick={cancel}>
<Button type="text" onClick={cancel}>
Cancel
</Button>
<div className="px-2"/>
<Button variant="outline" onClick={() => clipStore.next()}>Play Now</Button>
<Button type="default" onClick={() => clipStore.next()}>Play Now</Button>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
import React, { ErrorInfo } from 'react'
import { Button } from 'UI'
import React, { ErrorInfo } from 'react';
import { Button } from 'antd';
import { Icon } from 'UI';
class PlayerErrorBoundary extends React.Component<any> {
state = { hasError: false, error: '' };
@ -10,21 +11,20 @@ class PlayerErrorBoundary extends React.Component<any> {
componentDidCatch(error: Error, info: ErrorInfo) {
this.setState({
hasError: true,
error: error + info.componentStack
})
error: error + info.componentStack,
});
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div className={"flex flex-col p-4 gap-4"}>
<div className={'flex flex-col p-4 gap-4'}>
<h4>Something went wrong during player rendering.</h4>
<p>{this.state.error}</p>
<Button
onClick={() => window.location.reload()}
icon={"spinner"}
variant={"primary"}
icon={<Icon name={'spinner'} size={16} />}
type={'primary'}
style={{ width: 'fit-content' }}
>
Reload
@ -37,4 +37,4 @@ class PlayerErrorBoundary extends React.Component<any> {
}
}
export default PlayerErrorBoundary
export default PlayerErrorBoundary;

View file

@ -2,7 +2,8 @@ import React from 'react';
import { List } from 'immutable';
import cn from 'classnames';
import { withRequest, withToggle } from 'HOCs';
import { Button, Icon, SlideModal, TextEllipsis } from 'UI';
import { Icon, SlideModal, TextEllipsis } from 'UI';
import { Button } from 'antd'
import stl from './metadataItem.module.css';
import SessionList from './SessionList';
@ -62,7 +63,7 @@ export default class extends React.PureComponent {
</div>
<Button
onClick={ this.switchOpen }
variant="text"
type="text"
className={ stl.searchButton }
id="metadata-item"
>

View file

@ -2,7 +2,8 @@ import React from 'react';
import { List } from 'immutable';
import cn from 'classnames';
import { withRequest, withToggle } from 'HOCs';
import { Button, Icon, SlideModal, TextEllipsis } from 'UI';
import { Icon, SlideModal, TextEllipsis } from 'UI';
import { Button } from 'antd'
import stl from './metadataItem.module.css';
import SessionList from './SessionList';
@ -62,7 +63,7 @@ export default class extends React.PureComponent {
</div>
<Button
onClick={ this.switchOpen }
variant="text"
type="text"
className={ stl.searchButton }
id="metadata-item"
>

Some files were not shown because too many files have changed in this diff Show more