Ios conditions (#2039)

* feat(ui): condition set for mobile

* feat(ui): more filters for mobile, auto recognize mobile projects

* feat(ui): add memoryUsage filter for ios

* fix(ui): fix up filter name

* fix(ui): lost files

* fix(ui): cast filter value to string?

* feat(ui): condition set for mobile

* feat(ui): more filters for mobile, auto recognize mobile projects

* feat(ui): add memoryUsage filter for ios

* fix(ui): fix up filter name

* fix(ui): lost files

* fix(ui): cast filter value to string?
This commit is contained in:
Delirium 2024-04-05 11:05:08 +02:00 committed by GitHub
parent eaadcf4604
commit 44d934e956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 225 additions and 31 deletions

View file

@ -173,6 +173,7 @@ const Sites = ({ loading, sites, user, init }: PropsFromRedux) => {
setShowCaptureRate={setShowCaptureRate}
showCaptureRate={showCaptureRate}
projectId={activeProject?.id}
isMobile={activeProject?.platform !== 'web'}
open={showCaptureRate && !!activeProject}
/>
</Loader>

View file

@ -23,6 +23,7 @@ interface Props {
onChangeEventsOrder: (_: any, { name, value }: any) => void;
isConditional?: boolean;
changeName: (name: string) => void;
isMobile?: boolean;
}
function ConditionSetComponent({
@ -40,6 +41,7 @@ function ConditionSetComponent({
onRemoveFilter,
onChangeEventsOrder,
isConditional,
isMobile,
changeName,
}: Props) {
return (
@ -97,6 +99,7 @@ function ConditionSetComponent({
filter={undefined}
onFilterClick={onAddFilter}
excludeFilterKeys={excludeFilterKeys}
isMobile={isMobile}
>
<Button variant="text-primary" icon="plus">
Add Condition

View file

@ -15,6 +15,7 @@ interface Props {
setChanged?: (changed: boolean) => void;
excludeFilterKeys?: string[];
isConditional?: boolean;
isMobile?: boolean;
}
function ConditionSet({
@ -28,6 +29,7 @@ function ConditionSet({
setChanged,
excludeFilterKeys,
isConditional,
isMobile,
}: Props) {
const [forceRender, forceRerender] = React.useState(false);
@ -87,6 +89,7 @@ function ConditionSet({
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
isConditional={isConditional}
isMobile={isMobile}
/>
);
}

View file

@ -28,9 +28,7 @@ import {
} from 'lucide-react';
import React from 'react';
import { connect } from 'react-redux';
import { Icon, Loader } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { FilterKey } from '../../../../types/filter/filterType';
@ -122,21 +120,23 @@ export const getMatchingEntries = (
interface Props {
filters: any;
conditionalFilters: any;
mobileConditionalFilters: any;
onFilterClick?: (filter: any) => void;
filterSearchList: any;
// metaOptions: any,
isMainSearch?: boolean;
fetchingFilterSearchList: boolean;
searchQuery?: string;
excludeFilterKeys?: Array<string>;
allowedFilterKeys?: Array<string>;
isConditional?: boolean;
isMobile?: boolean;
}
function FilterModal(props: Props) {
const {
filters,
conditionalFilters,
mobileConditionalFilters,
onFilterClick = () => null,
filterSearchList,
isMainSearch = false,
@ -145,6 +145,7 @@ function FilterModal(props: Props) {
excludeFilterKeys = [],
allowedFilterKeys = [],
isConditional,
isMobile,
} = props;
const showSearchList = isMainSearch && searchQuery.length > 0;
@ -154,14 +155,12 @@ function FilterModal(props: Props) {
onFilterClick(_filter);
};
const filterJsonObj = isConditional
? isMobile ? mobileConditionalFilters : conditionalFilters
: filters;
const { matchingCategories, matchingFilters } = getMatchingEntries(
searchQuery,
filterJson(
isConditional ? conditionalFilters : filters,
excludeFilterKeys,
allowedFilterKeys
)
);
filterJson(filterJsonObj, excludeFilterKeys, allowedFilterKeys)
const isResultEmpty =
(!filterSearchList || Object.keys(filterSearchList).length === 0) &&
@ -275,6 +274,7 @@ export default connect((state: any, props: any) => {
? state.getIn(['search', 'filterListLive'])
: state.getIn(['search', 'filterList']),
conditionalFilters: state.getIn(['search', 'filterListConditional']),
mobileConditionalFilters: state.getIn(['search', 'filterListMobileConditional']),
filterSearchList: props.isLive
? state.getIn(['liveSearch', 'filterSearchList'])
: state.getIn(['search', 'filterSearchList']),

View file

@ -19,10 +19,11 @@ interface Props {
allowedFilterKeys?: Array<string>;
disabled?: boolean;
isConditional?: boolean;
isMobile?: boolean;
}
function FilterSelection(props: Props) {
const { filter, onFilterClick, children, excludeFilterKeys = [], allowedFilterKeys = [], disabled = false, isConditional } = props;
const { filter, onFilterClick, children, excludeFilterKeys = [], allowedFilterKeys = [], disabled = false, isConditional, isMobile } = props;
const [showModal, setShowModal] = useState(false);
return (
@ -68,6 +69,7 @@ function FilterSelection(props: Props) {
excludeFilterKeys={excludeFilterKeys}
allowedFilterKeys={allowedFilterKeys}
isConditional={isConditional}
isMobile={isMobile}
/>
</div>
)}

View file

@ -15,11 +15,12 @@ type Props = {
setShowCaptureRate: (show: boolean) => void;
open: boolean;
showCaptureRate: boolean;
isMobile?: boolean;
};
function CaptureRate(props: Props) {
const [conditions, setConditions] = React.useState<Conditions[]>([]);
const { isAdmin, projectId, isEnterprise } = props;
const { isAdmin, projectId, isEnterprise, isMobile } = props;
const { settingsStore } = useStore();
const [changed, setChanged] = useState(false);
const {
@ -42,7 +43,7 @@ function CaptureRate(props: Props) {
}, [projectId]);
React.useEffect(() => {
setConditions(captureConditions.map((condition: any) => new Conditions(condition, true)));
setConditions(captureConditions.map((condition: any) => new Conditions(condition, true, isMobile)));
}, [captureConditions]);
const onCaptureRateChange = (input: string) => {
@ -137,6 +138,7 @@ function CaptureRate(props: Props) {
setChanged={setChanged}
conditions={conditions}
setConditions={setConditions}
isMobile={isMobile}
/>
) : null}
</Tooltip>

View file

@ -8,10 +8,12 @@ function ConditionalRecordingSettings({
conditions,
setConditions,
setChanged,
isMobile,
}: {
setChanged: (changed: boolean) => void;
conditions: Conditions[];
setConditions: (conditions: Conditions[]) => void;
isMobile?: boolean;
}) {
const addConditionSet = () => {
setChanged(true);
@ -60,6 +62,7 @@ function ConditionalRecordingSettings({
setChanged={setChanged}
excludeFilterKeys={nonConditionalFlagFilters}
isConditional
isMobile={isMobile}
/>
{index !== conditions.length - 1 ? (
<div className={'text-disabled-text flex justify-center w-full'}>

View file

@ -6,6 +6,7 @@ import { createEdit, createInit } from './funcTools/crud';
import { createRequestReducer } from './funcTools/request';
import {
addElementToConditionalFiltersMap,
addElementToMobileConditionalFiltersMap,
addElementToFiltersMap,
addElementToFlagConditionsMap,
addElementToLiveFiltersMap,
@ -54,6 +55,7 @@ const reducer = (state = initialState, action = {}) => {
addElementToLiveFiltersMap(FilterCategory.METADATA, '_' + item.key);
addElementToFlagConditionsMap(FilterCategory.METADATA, '_' + item.key)
addElementToConditionalFiltersMap(FilterCategory.METADATA, '_' + item.key)
addElementToMobileConditionalFiltersMap(FilterCategory.METADATA, '_' + item.key)
});
return state.set('list', List(action.data).map(CustomField)).set('fetchedMetadata', true)

View file

@ -12,7 +12,8 @@ import {
filtersMap,
liveFiltersMap,
conditionalFiltersMap,
generateFilterOptions
generateFilterOptions,
mobileConditionalFiltersMap,
} from "Types/filter/newFilter";
import { DURATION_FILTER } from 'App/constants/storageKeys';
import Period, { CUSTOM_RANGE } from 'Types/app/period';
@ -20,7 +21,6 @@ import Period, { CUSTOM_RANGE } from 'Types/app/period';
const ERRORS_ROUTE = errorsRoute();
const name = 'search';
const idKey = 'searchId';
const PER_PAGE = 10;
const FETCH_LIST = fetchListType(name);
@ -43,19 +43,11 @@ const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS';
const CHECK_LATEST = fetchListType(`${name}/CHECK_LATEST`);
const UPDATE_LATEST_REQUEST_TIME = 'filters/UPDATE_LATEST_REQUEST_TIME'
// function chartWrapper(chart = []) {
// return chart.map((point) => ({ ...point, count: Math.max(point.count, 0) }));
// }
// const savedSearchIdKey = 'searchId';
// const updateItemInList = createListUpdater(savedSearchIdKey);
// const updateInstance = (state, instance) =>
// state.getIn(['savedSearch', savedSearchIdKey]) === instance[savedSearchIdKey] ? state.mergeIn(['savedSearch'], SavedFilter(instance)) : state;
const initialState = Map({
filterList: generateFilterOptions(filtersMap),
filterListLive: generateFilterOptions(liveFiltersMap),
filterListConditional: generateFilterOptions(conditionalFiltersMap),
filterListMobileConditional: generateFilterOptions(mobileConditionalFiltersMap),
list: List(),
latestRequestTime: null,
latestList: List(),
@ -76,7 +68,8 @@ function reducer(state = initialState, action = {}) {
return state
.set('filterList', generateFilterOptions(filtersMap))
.set('filterListLive', generateFilterOptions(liveFiltersMap))
.set('filterListConditional', generateFilterOptions(conditionalFiltersMap));
.set('filterListConditional', generateFilterOptions(conditionalFiltersMap))
.set('filterListMobileConditional', generateFilterOptions(mobileConditionalFiltersMap))
case EDIT:
return state.mergeIn(['instance'], action.instance).set('currentPage', 1);
case APPLY:

View file

@ -1,6 +1,6 @@
import { makeAutoObservable, observable, action } from 'mobx';
import { FilterKey, FilterType, FilterCategory } from 'Types/filter/filterType';
import { filtersMap, conditionalFiltersMap } from 'Types/filter/newFilter';
import { filtersMap, conditionalFiltersMap, mobileConditionalFiltersMap } from 'Types/filter/newFilter';
export default class FilterItem {
type: string = '';
@ -65,8 +65,12 @@ export default class FilterItem {
const isMetadata = json.type === FilterKey.METADATA;
let _filter: any = (isMetadata ? filtersMap['_' + json.source] : filtersMap[json.type]) || {};
if (this.isConditional) {
if (this.isMobile) {
_filter = mobileConditionalFiltersMap[json.type] || mobileConditionalFiltersMap[json.source];
} else {
_filter = conditionalFiltersMap[json.type] || conditionalFiltersMap[json.source];
}
}
if (mainFilterKey) {
const mainFilter = filtersMap[mainFilterKey];
const subFilterMap = {};
@ -108,7 +112,7 @@ export default class FilterItem {
const json = {
type: isMetadata ? FilterKey.METADATA : this.key,
isEvent: Boolean(this.isEvent),
value: this.value,
value: this.value.map((i: any) => i && i.toString()),
operator: this.operator,
source: isMetadata ? this.key.replace(/^_/, '') : this.source,
sourceOperator: this.sourceOperator,

View file

@ -658,12 +658,174 @@ export const conditionalFilters = [
return aOrder - bOrder
})
export const mobileConditionalFilters = [
{
key: FilterKey.DURATION,
type: FilterType.DURATION,
category: FilterCategory.RECORDING_ATTRIBUTES,
label: 'Duration',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is']),
icon: "filters/duration",
isEvent: false
},
{
key: FilterKey.FETCH,
type: FilterType.SUB_FILTERS,
category: FilterCategory.RECORDING_ATTRIBUTES,
operator: 'is',
label: 'Network Request',
filters: [
{
key: FilterKey.FETCH_URL,
type: FilterType.MULTIPLE,
category: FilterCategory.PERFORMANCE,
label: 'with URL',
placeholder: 'Enter path or URL',
operator: 'is',
operatorOptions: filterOptions.stringConditional,
icon: "filters/fetch"
},
{
key: FilterKey.FETCH_STATUS_CODE,
type: FilterType.NUMBER_MULTIPLE,
category: FilterCategory.PERFORMANCE,
label: 'with status code',
placeholder: 'Enter status code',
operator: '=',
operatorOptions: filterOptions.customOperators,
icon: "filters/fetch"
},
{
key: FilterKey.FETCH_METHOD,
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.PERFORMANCE,
label: 'with method',
operator: 'is',
placeholder: 'Select method type',
operatorOptions: filterOptions.stringOperatorsLimited,
icon: 'filters/fetch',
options: filterOptions.methodOptions
},
{
key: FilterKey.FETCH_DURATION,
type: FilterType.NUMBER,
category: FilterCategory.PERFORMANCE,
label: 'with duration (ms)',
placeholder: 'E.g. 12',
operator: '=',
operatorOptions: filterOptions.customOperators,
icon: "filters/fetch"
},
],
icon: 'filters/fetch',
isEvent: true
},
{
key: FilterKey.CUSTOM,
type: FilterType.MULTIPLE,
category: FilterCategory.RECORDING_ATTRIBUTES,
label: 'Custom Events',
placeholder: 'Enter event key',
operator: 'is',
operatorOptions: filterOptions.stringConditional,
icon: 'filters/custom',
isEvent: true
},
{
key: 'thermalState',
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.PERFORMANCE,
label: 'Device Thermal State',
placeholder: 'Pick an option',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is']),
icon: 'filters/cpu-load',
options: [
{ label: 'nominal', value: 0 },
{ label: 'warm', value: 1 },
{ label: 'hot', value: 2 },
{ label: 'critical', value: 3 }
],
},
{
key: 'mainThreadCPU',
type: FilterType.STRING,
category: FilterCategory.PERFORMANCE,
label: 'Main CPU Load %',
placeholder: '0 .. 100',
operator: '=',
operatorOptions: filterOptions.customOperators,
icon: 'filters/cpu-load',
},
{
key: 'viewComponent',
type: FilterType.STRING,
category: FilterCategory.RECORDING_ATTRIBUTES,
label: 'View on screen',
placeholder: 'View Name',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is']),
icon: 'filters/view',
},
{
key: FilterKey.USERID,
type: FilterType.MULTIPLE,
category: FilterCategory.USER,
label: 'User Id',
operator: 'isUndefined',
operatorOptions: [
{
key: 'isUndefined',
label: 'is undefined',
value: 'isUndefined'
},
{
key: 'isAny',
label: 'is present',
value: 'isAny'
}
],
icon: 'filters/userid'
},
{
key: 'logEvent',
type: FilterType.STRING,
category: FilterCategory.RECORDING_ATTRIBUTES,
label: 'Log in console',
placeholder: 'logged value',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/console',
},
{
key: 'clickEvent',
type: FilterType.STRING,
category: FilterCategory.INTERACTIONS,
label: 'Tap on view',
placeholder: 'View Name',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is']),
icon: 'filters/click'
},
{
key: 'memoryUsage',
type: FilterType.STRING,
category: FilterCategory.PERFORMANCE,
label: 'Memory usage %',
placeholder: '0 .. 100',
operatorOptions: filterOptions.customOperators,
icon: 'filters/memory-load'
}
]
export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key);
export const nonFlagFilters = filters.filter(i => {
return flagConditionFilters.findIndex(f => f.key === i.key) === -1;
}).map(i => i.key);
export const nonConditionalFlagFilters = filters.filter(i => {
return conditionalFilters.findIndex(f => f.key === i.key) === -1;
return conditionalFilters.findIndex(f => f.key === i.key) === -1
&& mobileConditionalFilters.findIndex(f => f.key === i.key) === -1;
}).map(i => i.key);
export const clickmapFilter = {
@ -717,6 +879,7 @@ export let filtersMap = mapFilters(filters);
export let liveFiltersMap = mapLiveFilters(filters);
export let fflagsConditionsMap = mapFilters(flagConditionFilters);
export let conditionalFiltersMap = mapFilters(conditionalFilters);
export let mobileConditionalFiltersMap = mapFilters(mobileConditionalFilters);
export const clearMetaFilters = () => {
filtersMap = mapFilters(filters);
@ -802,6 +965,26 @@ export const addElementToConditionalFiltersMap = (
};
};
export const addElementToMobileConditionalFiltersMap = (
category = FilterCategory.METADATA,
key,
type = FilterType.MULTIPLE,
operator = 'is',
operatorOptions = filterOptions.stringOperators,
icon = 'filters/metadata'
) => {
mobileConditionalFiltersMap[key] = {
key,
type,
category,
label: capitalize(key),
operator: operator,
operatorOptions,
icon,
isLive: true
}
}
export const addElementToLiveFiltersMap = (
category = FilterCategory.METADATA,
key,
@ -829,11 +1012,9 @@ export default Record({
icon: '',
type: '',
value: [''],
source: [''],
category: '',
custom: '',
// target: Target(),
level: '',
hasNoValue: false,