Merge branch 'dev' into insights_fix
This commit is contained in:
commit
104356bbe2
29 changed files with 294 additions and 67 deletions
|
|
@ -27,7 +27,7 @@ RUN adduser -u 1001 openreplay -D
|
|||
|
||||
ENV TZ=UTC \
|
||||
GIT_SHA=$GIT_SHA \
|
||||
FS_ULIMIT=1000 \
|
||||
FS_ULIMIT=10000 \
|
||||
FS_DIR=/mnt/efs \
|
||||
MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \
|
||||
UAPARSER_FILE=/home/openreplay/regexes.yaml \
|
||||
|
|
@ -76,8 +76,8 @@ ENV TZ=UTC \
|
|||
USE_FAILOVER=false \
|
||||
GROUP_STORAGE_FAILOVER=failover \
|
||||
TOPIC_STORAGE_FAILOVER=storage-failover \
|
||||
PROFILER_ENABLED=false
|
||||
|
||||
PROFILER_ENABLED=false \
|
||||
COMPRESSION_TYPE=zstd
|
||||
|
||||
|
||||
ARG SERVICE_NAME
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ func (c *cacher) cacheURL(t *Task) {
|
|||
defer res.Body.Close()
|
||||
if res.StatusCode >= 400 {
|
||||
printErr := true
|
||||
// Retry 403 error
|
||||
if res.StatusCode == 403 && t.retries > 0 {
|
||||
// Retry 403/503 errors
|
||||
if (res.StatusCode == 403 || res.StatusCode == 503) && t.retries > 0 {
|
||||
c.workers.AddTask(t)
|
||||
printErr = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Config struct {
|
|||
ProducerCloseTimeout int `env:"PRODUCER_CLOSE_TIMEOUT,default=15000"`
|
||||
UseFailover bool `env:"USE_FAILOVER,default=false"`
|
||||
MaxFileSize int64 `env:"MAX_FILE_SIZE,default=524288000"`
|
||||
UseSort bool `env:"USE_SESSION_SORT,default=true"`
|
||||
UseProfiler bool `env:"PROFILER_ENABLED,default=false"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ type Storage struct {
|
|||
sessionDEVSize syncfloat64.Histogram
|
||||
readingDOMTime syncfloat64.Histogram
|
||||
readingDEVTime syncfloat64.Histogram
|
||||
sortingDOMTime syncfloat64.Histogram
|
||||
sortingDEVTime syncfloat64.Histogram
|
||||
archivingDOMTime syncfloat64.Histogram
|
||||
archivingDEVTime syncfloat64.Histogram
|
||||
uploadingDOMTime syncfloat64.Histogram
|
||||
|
|
@ -79,6 +81,14 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor
|
|||
if err != nil {
|
||||
log.Printf("can't create reading_duration metric: %s", err)
|
||||
}
|
||||
sortingDOMTime, err := metrics.RegisterHistogram("sorting_duration")
|
||||
if err != nil {
|
||||
log.Printf("can't create reading_duration metric: %s", err)
|
||||
}
|
||||
sortingDEVTime, err := metrics.RegisterHistogram("sorting_dt_duration")
|
||||
if err != nil {
|
||||
log.Printf("can't create reading_duration metric: %s", err)
|
||||
}
|
||||
archivingDOMTime, err := metrics.RegisterHistogram("archiving_duration")
|
||||
if err != nil {
|
||||
log.Printf("can't create archiving_duration metric: %s", err)
|
||||
|
|
@ -104,6 +114,8 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor
|
|||
sessionDEVSize: sessionDevtoolsSize,
|
||||
readingDOMTime: readingDOMTime,
|
||||
readingDEVTime: readingDEVTime,
|
||||
sortingDOMTime: sortingDOMTime,
|
||||
sortingDEVTime: sortingDEVTime,
|
||||
archivingDOMTime: archivingDOMTime,
|
||||
archivingDEVTime: archivingDEVTime,
|
||||
uploadingDOMTime: uploadingDOMTime,
|
||||
|
|
@ -156,14 +168,41 @@ func (s *Storage) Upload(msg *messages.SessionEnd) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) openSession(filePath string) ([]byte, error) {
|
||||
func (s *Storage) openSession(filePath string, tp FileType) ([]byte, error) {
|
||||
// Check file size before download into memory
|
||||
info, err := os.Stat(filePath)
|
||||
if err == nil && info.Size() > s.cfg.MaxFileSize {
|
||||
return nil, fmt.Errorf("big file, size: %d", info.Size())
|
||||
}
|
||||
// Read file into memory
|
||||
return os.ReadFile(filePath)
|
||||
raw, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !s.cfg.UseSort {
|
||||
return raw, nil
|
||||
}
|
||||
start := time.Now()
|
||||
res, err := s.sortSessionMessages(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sort session, err: %s", err)
|
||||
}
|
||||
if tp == DOM {
|
||||
s.sortingDOMTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()))
|
||||
} else {
|
||||
s.sortingDEVTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Storage) sortSessionMessages(raw []byte) ([]byte, error) {
|
||||
// Parse messages, sort by index and save result into slice of bytes
|
||||
unsortedMessages, err := messages.SplitMessages(raw)
|
||||
if err != nil {
|
||||
log.Printf("can't sort session, err: %s", err)
|
||||
return raw, nil
|
||||
}
|
||||
return messages.MergeMessages(raw, messages.SortMessages(unsortedMessages)), nil
|
||||
}
|
||||
|
||||
func (s *Storage) prepareSession(path string, tp FileType, task *Task) error {
|
||||
|
|
@ -172,7 +211,7 @@ func (s *Storage) prepareSession(path string, tp FileType, task *Task) error {
|
|||
path += "devtools"
|
||||
}
|
||||
startRead := time.Now()
|
||||
mob, err := s.openSession(path)
|
||||
mob, err := s.openSession(path, tp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package messages
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -13,6 +14,7 @@ type BytesReader interface {
|
|||
ReadInt() (int64, error)
|
||||
ReadBoolean() (bool, error)
|
||||
ReadString() (string, error)
|
||||
ReadIndex() (uint64, error)
|
||||
Data() []byte
|
||||
Pointer() int64
|
||||
SetPointer(p int64)
|
||||
|
|
@ -106,6 +108,15 @@ func (m *bytesReaderImpl) ReadString() (string, error) {
|
|||
return str, nil
|
||||
}
|
||||
|
||||
func (m *bytesReaderImpl) ReadIndex() (uint64, error) {
|
||||
if len(m.data)-int(m.curr) < 8 {
|
||||
return 0, fmt.Errorf("out of range")
|
||||
}
|
||||
size := binary.LittleEndian.Uint64(m.data[m.curr : m.curr+8])
|
||||
m.curr += 8
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (m *bytesReaderImpl) Data() []byte {
|
||||
return m.data
|
||||
}
|
||||
|
|
|
|||
71
backend/pkg/messages/session-iterator.go
Normal file
71
backend/pkg/messages/session-iterator.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package messages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type msgInfo struct {
|
||||
index uint64
|
||||
start int64
|
||||
end int64
|
||||
}
|
||||
|
||||
func SplitMessages(data []byte) ([]*msgInfo, error) {
|
||||
messages := make([]*msgInfo, 0)
|
||||
reader := NewBytesReader(data)
|
||||
for {
|
||||
// Get message start
|
||||
msgStart := reader.Pointer()
|
||||
if int(msgStart) >= len(data) {
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// Read message index
|
||||
msgIndex, err := reader.ReadIndex()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Println(reader.Pointer(), msgStart)
|
||||
return nil, fmt.Errorf("read message index err: %s", err)
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// Read message type
|
||||
msgType, err := reader.ReadUint()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read message type err: %s", err)
|
||||
}
|
||||
|
||||
// Read message body
|
||||
_, err = ReadMessage(msgType, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read message body err: %s", err)
|
||||
}
|
||||
|
||||
// Add new message info to messages slice
|
||||
messages = append(messages, &msgInfo{
|
||||
index: msgIndex,
|
||||
start: msgStart,
|
||||
end: reader.Pointer(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func SortMessages(messages []*msgInfo) []*msgInfo {
|
||||
sort.SliceStable(messages, func(i, j int) bool {
|
||||
return messages[i].index < messages[j].index
|
||||
})
|
||||
return messages
|
||||
}
|
||||
|
||||
func MergeMessages(data []byte, messages []*msgInfo) []byte {
|
||||
sortedSession := bytes.NewBuffer(make([]byte, 0, len(data)))
|
||||
for _, info := range messages {
|
||||
sortedSession.Write(data[info.start:info.end])
|
||||
}
|
||||
return sortedSession.Bytes()
|
||||
}
|
||||
|
|
@ -15,13 +15,20 @@ type Producer struct {
|
|||
|
||||
func NewProducer(messageSizeLimit int, useBatch bool) *Producer {
|
||||
kafkaConfig := &kafka.ConfigMap{
|
||||
"enable.idempotence": true,
|
||||
"bootstrap.servers": env.String("KAFKA_SERVERS"),
|
||||
"go.delivery.reports": true,
|
||||
"security.protocol": "plaintext",
|
||||
"go.batch.producer": useBatch,
|
||||
"queue.buffering.max.ms": 100,
|
||||
"message.max.bytes": messageSizeLimit,
|
||||
"enable.idempotence": true,
|
||||
"bootstrap.servers": env.String("KAFKA_SERVERS"),
|
||||
"go.delivery.reports": true,
|
||||
"security.protocol": "plaintext",
|
||||
"go.batch.producer": useBatch,
|
||||
"message.max.bytes": messageSizeLimit, // should be synced with broker config
|
||||
"linger.ms": 1000,
|
||||
"queue.buffering.max.ms": 1000,
|
||||
"batch.num.messages": 1000,
|
||||
"queue.buffering.max.messages": 1000,
|
||||
"retries": 3,
|
||||
"retry.backoff.ms": 100,
|
||||
"max.in.flight.requests.per.connection": 1,
|
||||
"compression.type": env.String("COMPRESSION_TYPE"),
|
||||
}
|
||||
// Apply ssl configuration
|
||||
if env.Bool("KAFKA_USE_SSL") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import logger from 'App/logger';
|
||||
import APIClient from './api_client';
|
||||
import { UPDATE_JWT } from './duck/user';
|
||||
import { LOGIN, UPDATE_JWT } from './duck/user';
|
||||
|
||||
export default () => (next) => (action) => {
|
||||
const { types, call, ...rest } = action;
|
||||
|
|
@ -14,7 +14,7 @@ export default () => (next) => (action) => {
|
|||
return call(client)
|
||||
.then(async (response) => {
|
||||
if (response.status === 403) {
|
||||
next({ type: UPDATE_JWT, data: null });
|
||||
next({ type: LOGIN.FAILURE });
|
||||
}
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ interface Props {
|
|||
series: any;
|
||||
onRemoveSeries: (seriesIndex: any) => void;
|
||||
canDelete?: boolean;
|
||||
|
||||
supportsEmpty?: boolean;
|
||||
hideHeader?: boolean;
|
||||
emptyMessage?: any;
|
||||
observeChanges?: () => void;
|
||||
excludeFilterKeys?: Array<string>
|
||||
}
|
||||
|
||||
function FilterSeries(props: Props) {
|
||||
|
|
@ -23,7 +24,9 @@ function FilterSeries(props: Props) {
|
|||
},
|
||||
canDelete,
|
||||
hideHeader = false,
|
||||
emptyMessage = 'Add user event or filter to define the series by clicking Add Step.'
|
||||
emptyMessage = 'Add user event or filter to define the series by clicking Add Step.',
|
||||
supportsEmpty = true,
|
||||
excludeFilterKeys = []
|
||||
} = props;
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const { series, seriesIndex } = props;
|
||||
|
|
@ -74,6 +77,8 @@ function FilterSeries(props: Props) {
|
|||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
supportsEmpty={supportsEmpty}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
/>
|
||||
) : (
|
||||
<div className="color-gray-medium">{emptyMessage}</div>
|
||||
|
|
@ -84,6 +89,7 @@ function FilterSeries(props: Props) {
|
|||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
>
|
||||
<Button variant="text-primary" icon="plus">ADD STEP</Button>
|
||||
</FilterSelection>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ import {
|
|||
PERFORMANCE,
|
||||
WEB_VITALS,
|
||||
} from 'App/constants/card';
|
||||
import { clickmapFilter } from 'App/types/filter/newFilter';
|
||||
import { clickmapFilter, eventKeys } from 'App/types/filter/newFilter';
|
||||
import { renderClickmapThumbnail } from './renderMap';
|
||||
|
||||
interface Props {
|
||||
history: any;
|
||||
match: any;
|
||||
|
|
@ -51,6 +50,8 @@ function WidgetForm(props: Props) {
|
|||
metric.metricType
|
||||
);
|
||||
|
||||
const excludeFilterKeys = isClickmap ? eventKeys : []
|
||||
|
||||
const writeOption = ({ value, name }: { value: any; name: any }) => {
|
||||
value = Array.isArray(value) ? value : value.value;
|
||||
const obj: any = { [name]: value };
|
||||
|
|
@ -201,6 +202,8 @@ function WidgetForm(props: Props) {
|
|||
.map((series: any, index: number) => (
|
||||
<div className="mb-2" key={series.name}>
|
||||
<FilterSeries
|
||||
supportsEmpty={!isClickmap}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
observeChanges={() => metric.updateKey('hasChanged', true)}
|
||||
hideHeader={isTable || isClickmap}
|
||||
seriesIndex={index}
|
||||
|
|
|
|||
|
|
@ -122,15 +122,15 @@ export default class Login extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
</Loader>
|
||||
{ errors &&
|
||||
<div className={ stl.errors }>
|
||||
{ errors.length ?
|
||||
(<div className={ stl.errors }>
|
||||
{ errors.map(error => (
|
||||
<div className={stl.errorItem}>
|
||||
<Icon name="info" color="red" size="20"/>
|
||||
<span className="color-red ml-2">{ error }<br /></span>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
</div>) : null
|
||||
}
|
||||
{/* <div className={ stl.formFooter }> */}
|
||||
<Button className="mt-2" type="submit" variant="primary" >{ 'Login' }</Button>
|
||||
|
|
|
|||
|
|
@ -108,5 +108,5 @@ $offset: 10px;
|
|||
}
|
||||
|
||||
.inactiveRow {
|
||||
opacity: 0.5;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ function AlertTriggersModal(props: Props) {
|
|||
}, [])
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}>
|
||||
<div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '350px'}}>
|
||||
<div className="flex items-center justify-between p-5 text-2xl">
|
||||
<div>Alerts</div>
|
||||
{ count > 0 && (
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
'error color-red': !!row.isRed && row.isRed(),
|
||||
'cursor-pointer': typeof onRowClick === 'function',
|
||||
[stl.activeRow]: activeIndex === index,
|
||||
// [stl.inactiveRow]: !activeIndex || index > activeIndex,
|
||||
[stl.inactiveRow]: !activeIndex || index > activeIndex,
|
||||
}
|
||||
)}
|
||||
onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import FilterOperator from '../FilterOperator';
|
||||
import FilterSelection from '../FilterSelection';
|
||||
import FilterValue from '../FilterValue';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, Button } from 'UI';
|
||||
import FilterSource from '../FilterSource';
|
||||
import { FilterKey, FilterType } from 'App/types/filter/filterType';
|
||||
import SubFilterItem from '../SubFilterItem';
|
||||
|
|
@ -14,9 +14,11 @@ interface Props {
|
|||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
saveRequestPayloads?: boolean;
|
||||
disableDelete?: boolean;
|
||||
excludeFilterKeys?: Array<string>;
|
||||
}
|
||||
function FilterItem(props: Props) {
|
||||
const { isFilter = false, filterIndex, filter, saveRequestPayloads } = props;
|
||||
const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false, excludeFilterKeys = [] } = props;
|
||||
const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined');
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
|
||||
|
|
@ -49,14 +51,14 @@ function FilterItem(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2">
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5">
|
||||
<div className="flex items-start w-full">
|
||||
{!isFilter && (
|
||||
<div className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2">
|
||||
<span>{filterIndex + 1}</span>
|
||||
</div>
|
||||
)}
|
||||
<FilterSelection filter={filter} onFilterClick={replaceFilter} />
|
||||
<FilterSelection filter={filter} onFilterClick={replaceFilter} excludeFilterKeys={excludeFilterKeys} disabled={disableDelete} />
|
||||
|
||||
{/* Filter with Source */}
|
||||
{filter.hasSource && (
|
||||
|
|
@ -102,10 +104,8 @@ function FilterItem(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2">
|
||||
<div className="cursor-pointer p-1" onClick={props.onRemoveFilter}>
|
||||
<Icon name="trash" size="14" />
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start ml-auto">
|
||||
<Button disabled={disableDelete} variant="text" icon="trash" onClick={props.onRemoveFilter} size="small" iconSize={14} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@ interface Props {
|
|||
hideEventsOrder?: boolean;
|
||||
observeChanges?: () => void;
|
||||
saveRequestPayloads?: boolean;
|
||||
supportsEmpty?: boolean
|
||||
excludeFilterKeys?: Array<string>
|
||||
}
|
||||
function FilterList(props: Props) {
|
||||
const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads } = props;
|
||||
const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true, excludeFilterKeys = [] } = props;
|
||||
const filters = List(filter.filters);
|
||||
const hasEvents = filters.filter((i: any) => i.isEvent).size > 0;
|
||||
const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0;
|
||||
let rowIndex = 0;
|
||||
const cannotDeleteFilter = hasEvents && !supportsEmpty;
|
||||
|
||||
useEffect(observeChanges, [filters]);
|
||||
|
||||
|
|
@ -69,6 +72,8 @@ function FilterList(props: Props) {
|
|||
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
|
||||
onRemoveFilter={() => onRemoveFilter(filterIndex)}
|
||||
saveRequestPayloads={saveRequestPayloads}
|
||||
disableDelete={cannotDeleteFilter}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
|
|
@ -89,6 +94,7 @@ function FilterList(props: Props) {
|
|||
filter={filter}
|
||||
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
|
||||
onRemoveFilter={() => onRemoveFilter(filterIndex)}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
|
|
@ -6,10 +6,27 @@ import stl from './FilterModal.module.css';
|
|||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
|
||||
function filterJson(
|
||||
jsonObj: Record<string, any>,
|
||||
excludeKeys: string[] = []
|
||||
): Record<string, any> {
|
||||
let filtered: Record<string, any> = {};
|
||||
|
||||
for (const key in jsonObj) {
|
||||
const arr = jsonObj[key].filter((i: any) => !excludeKeys.includes(i.key));
|
||||
if (arr.length) {
|
||||
filtered[key] = arr;
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
export const getMatchingEntries = (searchQuery: string, filters: Record<string, any>) => {
|
||||
const matchingCategories: string[] = [];
|
||||
const matchingFilters: Record<string, any> = {};
|
||||
const lowerCaseQuery = searchQuery.toLowerCase();
|
||||
|
||||
if (lowerCaseQuery.length === 0) return {
|
||||
matchingCategories: Object.keys(filters),
|
||||
matchingFilters: filters,
|
||||
|
|
@ -33,12 +50,13 @@ export const getMatchingEntries = (searchQuery: string, filters: Record<string,
|
|||
|
||||
interface Props {
|
||||
filters: any,
|
||||
onFilterClick?: (filter) => void,
|
||||
onFilterClick?: (filter: any) => void,
|
||||
filterSearchList: any,
|
||||
// metaOptions: any,
|
||||
isMainSearch?: boolean,
|
||||
fetchingFilterSearchList: boolean,
|
||||
searchQuery?: string,
|
||||
excludeFilterKeys?: Array<string>
|
||||
}
|
||||
function FilterModal(props: Props) {
|
||||
const {
|
||||
|
|
@ -48,6 +66,7 @@ function FilterModal(props: Props) {
|
|||
isMainSearch = false,
|
||||
fetchingFilterSearchList,
|
||||
searchQuery = '',
|
||||
excludeFilterKeys = []
|
||||
} = props;
|
||||
const showSearchList = isMainSearch && searchQuery.length > 0;
|
||||
|
||||
|
|
@ -57,7 +76,7 @@ function FilterModal(props: Props) {
|
|||
onFilterClick(_filter);
|
||||
}
|
||||
|
||||
const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filters);
|
||||
const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filterJson(filters, excludeFilterKeys));
|
||||
|
||||
const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).length === 0)
|
||||
&& matchingCategories.length === 0 && Object.keys(matchingFilters).length === 0
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import FilterModal from '../FilterModal';
|
|||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import { Icon } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { assist as assistRoute, isRoute } from "App/routes";
|
||||
import { assist as assistRoute, isRoute } from 'App/routes';
|
||||
import cn from 'classnames';
|
||||
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
|
|
@ -14,40 +15,54 @@ interface Props {
|
|||
onFilterClick: (filter: any) => void;
|
||||
children?: any;
|
||||
isLive?: boolean;
|
||||
excludeFilterKeys?: Array<string>
|
||||
disabled?: boolean
|
||||
}
|
||||
function FilterSelection(props: Props) {
|
||||
const { filter, onFilterClick, children } = props;
|
||||
const { filter, onFilterClick, children, excludeFilterKeys = [], disabled = false } = props;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative flex-shrink-0">
|
||||
<OutsideClickDetectingDiv
|
||||
className="relative"
|
||||
onClickOutside={ () => setTimeout(function() {
|
||||
setShowModal(false)
|
||||
}, 200)}
|
||||
onClickOutside={() =>
|
||||
setTimeout(function () {
|
||||
setShowModal(false);
|
||||
}, 200)
|
||||
}
|
||||
>
|
||||
{ children ? React.cloneElement(children, { onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowModal(true);
|
||||
}}) : (
|
||||
{children ? (
|
||||
React.cloneElement(children, {
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowModal(true);
|
||||
},
|
||||
disabled: disabled
|
||||
})
|
||||
) : (
|
||||
<div
|
||||
className="rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade"
|
||||
className={cn("rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade", { 'opacity-50 pointer-events-none' : disabled })}
|
||||
style={{ width: '150px', height: '26px', border: 'solid thin #e9e9e9' }}
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<div className="overflow-hidden whitespace-nowrap text-ellipsis mr-auto truncate" style={{ textOverflow: 'ellipsis'}}>{filter.label}</div>
|
||||
<div
|
||||
className="overflow-hidden whitespace-nowrap text-ellipsis mr-auto truncate"
|
||||
style={{ textOverflow: 'ellipsis' }}
|
||||
>
|
||||
{filter.label}
|
||||
</div>
|
||||
<Icon name="chevron-down" size="14" />
|
||||
</div>
|
||||
) }
|
||||
)}
|
||||
</OutsideClickDetectingDiv>
|
||||
{showModal && (
|
||||
<div className="absolute left-0 border shadow rounded bg-white z-50">
|
||||
<FilterModal
|
||||
isLive={isRoute(ASSIST_ROUTE, window.location.pathname)}
|
||||
onFilterClick={onFilterClick}
|
||||
// filters={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterListLive : props.filterList }
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -55,8 +70,11 @@ function FilterSelection(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
filterList: state.getIn([ 'search', 'filterList' ]),
|
||||
filterListLive: state.getIn([ 'search', 'filterListLive' ]),
|
||||
isLive: state.getIn([ 'sessions', 'activeTab' ]).type === 'live',
|
||||
}), { })(FilterSelection);
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
filterList: state.getIn(['search', 'filterList']),
|
||||
filterListLive: state.getIn(['search', 'filterListLive']),
|
||||
isLive: state.getIn(['sessions', 'activeTab']).type === 'live',
|
||||
}),
|
||||
{}
|
||||
)(FilterSelection);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@ export const initialState = Map({
|
|||
authDetails: {},
|
||||
onboarding: false,
|
||||
sites: List(),
|
||||
jwt: null
|
||||
jwt: null,
|
||||
loginRequest: {
|
||||
loading: false,
|
||||
errors: []
|
||||
},
|
||||
});
|
||||
|
||||
const setClient = (state, data) => {
|
||||
|
|
@ -50,10 +54,12 @@ const reducer = (state = initialState, action = {}) => {
|
|||
switch (action.type) {
|
||||
case UPDATE_JWT:
|
||||
return state.set('jwt', action.data);
|
||||
case LOGIN.REQUEST:
|
||||
return state.set('loginRequest', { loading: true, errors: [] })
|
||||
case RESET_PASSWORD.SUCCESS:
|
||||
case UPDATE_PASSWORD.SUCCESS:
|
||||
case LOGIN.SUCCESS:
|
||||
state.set('account', Account({...action.data.user }))
|
||||
state.set('account', Account({...action.data.user })).set('loginRequest', { loading: false, errors: [] })
|
||||
case SIGNUP.SUCCESS:
|
||||
state.set('account', Account(action.data.user)).set('onboarding', true);
|
||||
case REQUEST_RESET_PASSWORD.SUCCESS:
|
||||
|
|
@ -65,8 +71,10 @@ const reducer = (state = initialState, action = {}) => {
|
|||
return state.set('authDetails', action.data);
|
||||
case UPDATE_PASSWORD.FAILURE:
|
||||
return state.set('passwordErrors', List(action.errors))
|
||||
case FETCH_ACCOUNT.FAILURE:
|
||||
case LOGIN.FAILURE:
|
||||
deleteCookie('jwt', '/', 'openreplay.com')
|
||||
return state.set('loginRequest', { loading: false, errors: ['Invalid username or password'] });
|
||||
case FETCH_ACCOUNT.FAILURE:
|
||||
case DELETE.SUCCESS:
|
||||
case DELETE.FAILURE:
|
||||
deleteCookie('jwt', '/', 'openreplay.com')
|
||||
|
|
@ -86,7 +94,6 @@ const reducer = (state = initialState, action = {}) => {
|
|||
|
||||
|
||||
export default withRequestState({
|
||||
loginRequest: LOGIN,
|
||||
signupRequest: SIGNUP,
|
||||
updatePasswordRequest: UPDATE_PASSWORD,
|
||||
requestResetPassowrd: REQUEST_RESET_PASSWORD,
|
||||
|
|
@ -96,7 +103,7 @@ export default withRequestState({
|
|||
updateAccountRequest: UPDATE_ACCOUNT,
|
||||
}, reducer);
|
||||
|
||||
export const login = params => dispatch => dispatch({
|
||||
export const login = params => ({
|
||||
types: LOGIN.toArray(),
|
||||
call: client => client.post('/login', params),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,19 +26,18 @@ export default class WebPlayer extends Player {
|
|||
private targetMarker: TargetMarker
|
||||
|
||||
constructor(protected wpState: Store<typeof WebPlayer.INITIAL_STATE>, session: any, live: boolean) {
|
||||
console.log(session.events, session.stackEvents, session.resources, session.errors)
|
||||
let initialLists = live ? {} : {
|
||||
event: session.events,
|
||||
event: session.events || [],
|
||||
stack: session.stackEvents || [],
|
||||
resource: session.resources || [], // MBTODO: put ResourceTiming in file
|
||||
exceptions: session.errors.map(({ time, errorId, name }: any) =>
|
||||
exceptions: session.errors?.map(({ time, errorId, name }: any) =>
|
||||
Log({
|
||||
level: LogLevel.ERROR,
|
||||
value: name,
|
||||
time,
|
||||
errorId,
|
||||
})
|
||||
),
|
||||
) || [],
|
||||
}
|
||||
|
||||
const screen = new Screen(session.isMobile)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import apiMiddleware from './api_middleware';
|
|||
import LocalStorage from './local_storage';
|
||||
import { initialState as initUserState, UPDATE_JWT } from './duck/user'
|
||||
|
||||
// TODO @remove after few days
|
||||
localStorage.removeItem('jwt')
|
||||
|
||||
const storage = new LocalStorage({
|
||||
user: Object,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export const filters = [
|
|||
{ key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', placeholder: 'Select an issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions },
|
||||
];
|
||||
|
||||
export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key);
|
||||
|
||||
export const clickmapFilter = {
|
||||
key: FilterKey.LOCATION,
|
||||
type: FilterType.MULTIPLE,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ spec:
|
|||
spec:
|
||||
backoffLimit: 0 # Don't restart the failed jobs
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ spec:
|
|||
spec:
|
||||
backoffLimit: 0 # Don't restart the failed jobs
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ spec:
|
|||
spec:
|
||||
backoffLimit: 0 # Don't restart the failed jobs
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ spec:
|
|||
spec:
|
||||
backoffLimit: 0 # Don't restart the failed jobs
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ cron: "5 3 */3 * *"
|
|||
|
||||
# Pod configurations
|
||||
|
||||
podAnnotations:
|
||||
linkerd.io/inject: disabled
|
||||
|
||||
securityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ spec:
|
|||
template:
|
||||
metadata:
|
||||
name: postgresqlMigrate
|
||||
{{- with .Values.migrationJob.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
initContainers:
|
||||
- name: git
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
migrationJob:
|
||||
podAnnotations:
|
||||
linkerd.io/inject: disabled
|
||||
|
||||
redis: &redis
|
||||
tls:
|
||||
enabled: false
|
||||
|
|
@ -5,6 +9,10 @@ redis: &redis
|
|||
ingress-nginx:
|
||||
enabled: true
|
||||
controller:
|
||||
admissionWebhooks:
|
||||
patch:
|
||||
podAnnotations:
|
||||
linkerd.io/inject: disabled
|
||||
name: controller
|
||||
image:
|
||||
registry: k8s.gcr.io
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue