pulled main
This commit is contained in:
commit
1299e14475
199 changed files with 1251 additions and 2809 deletions
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
2
ee/api/.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const defaultOptions = {
|
|||
extraCssText: 'box-shadow: none; pointer-events: auto;',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
snap: true,
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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 don’t have the permissions to perform this action."
|
||||
disabled={isAdmin}
|
||||
>
|
||||
<Button variant="primary" onClick={() => editHandler({})}>
|
||||
<Button type="primary" onClick={() => editHandler({})}>
|
||||
Add
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function DashboardEditModal(props: Props) {
|
|||
value={ dashboard.name }
|
||||
onChange={write}
|
||||
placeholder="Title"
|
||||
maxLength={100}
|
||||
maxLength={40}
|
||||
autoFocus={focusTitle}
|
||||
/>
|
||||
</Form.Field>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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' }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './FunnelDetails'
|
||||
|
|
@ -1 +0,0 @@
|
|||
//export { default } from './FunnelIssueDetails'
|
||||
|
|
@ -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;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './FunnelList';
|
||||
|
|
@ -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;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './FunnelPage';
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?.()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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]}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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')}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue