Merge pull request #918 from openreplay/dashboard-cards

feat(ui) - cards - changes
This commit is contained in:
Shekar Siri 2023-01-09 16:13:40 +01:00 committed by GitHub
commit 78eeee0e2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 40 deletions

View file

@ -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>

View file

@ -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}

View file

@ -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 && (

View file

@ -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>
);

View file

@ -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
)}

View file

@ -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

View file

@ -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);

View file

@ -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)

View file

@ -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,