Skip to content

Commit

Permalink
feat(frontend): implement ud attr filters
Browse files Browse the repository at this point in the history
closes #1531
  • Loading branch information
anupcowkur committed Jan 19, 2025
1 parent 613edd9 commit 6b759c4
Show file tree
Hide file tree
Showing 12 changed files with 1,267 additions and 207 deletions.
1 change: 1 addition & 0 deletions frontend/dashboard/app/[teamId]/apps/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export default function Apps({ params }: { params: { teamId: string } }) {
showLocales={false}
showDeviceManufacturers={false}
showDeviceNames={false}
showUdAttrs={false}
showFreeText={false}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />

Expand Down
1 change: 1 addition & 0 deletions frontend/dashboard/app/[teamId]/overview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function Overview({ params }: { params: { teamId: string } }) {
showDeviceManufacturers={false}
showDeviceNames={false}
showFreeText={false}
showUdAttrs={false}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />

<div className="py-4" />
Expand Down
1 change: 1 addition & 0 deletions frontend/dashboard/app/[teamId]/sessions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default function SessionsOverview({ params }: { params: { teamId: string
showLocales={true}
showDeviceManufacturers={true}
showDeviceNames={true}
showUdAttrs={true}
showFreeText={true}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />
<div className="py-4" />
Expand Down
1 change: 1 addition & 0 deletions frontend/dashboard/app/[teamId]/traces/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default function TracesOverview({ params }: { params: { teamId: string }
showLocales={true}
showDeviceManufacturers={true}
showDeviceNames={true}
showUdAttrs={false}
showFreeText={false}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />
<div className="py-4" />
Expand Down
61 changes: 44 additions & 17 deletions frontend/dashboard/app/api/api_calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,11 @@ export class OsVersion {
}
}

export type UserDefAttr = {
key: string
type: string
}

export const saveListFiltersToServer = async (filters: Filters) => {
if (filters.versions.length === 0 &&
filters.osVersions.length === 0 &&
Expand All @@ -828,29 +833,48 @@ export const saveListFiltersToServer = async (filters: Filters) => {
filters.networkGenerations.length === 0 &&
filters.locales.length === 0 &&
filters.deviceManufacturers.length === 0 &&
filters.deviceNames.length === 0
filters.deviceNames.length === 0 &&
filters.udAttrMatchers.length === 0
) {
return null
}

const origin = process.env.NEXT_PUBLIC_API_BASE_URL
let url = `${origin}/apps/${filters.app.id}/shortFilters`

const udExpression = {
and: filters.udAttrMatchers.map(matcher => ({
cmp: {
key: matcher.key,
type: matcher.type,
op: matcher.op,
value: String(matcher.value)
}
}))
};

const bodyFilters: any = {
versions: filters.versions.map((v) => v.name),
version_codes: filters.versions.map((v) => v.code),
os_names: filters.osVersions.map((v) => v.name),
os_versions: filters.osVersions.map((v) => v.version),
countries: filters.countries,
network_providers: filters.networkProviders,
network_types: filters.networkTypes,
network_generations: filters.networkGenerations,
locales: filters.locales,
device_manufacturers: filters.deviceManufacturers,
device_names: filters.deviceNames,
};

if (filters.udAttrMatchers.length > 0) {
bodyFilters.ud_expression = JSON.stringify(udExpression);
}

const opts = {
method: 'POST',
body: JSON.stringify({
filters: {
versions: filters.versions.map((v) => v.name),
version_codes: filters.versions.map((v) => v.code),
os_names: filters.osVersions.map((v) => v.name),
os_versions: filters.osVersions.map((v) => v.version),
countries: filters.countries,
network_providers: filters.networkProviders,
network_types: filters.networkTypes,
network_generations: filters.networkGenerations,
locales: filters.locales,
device_manufacturers: filters.deviceManufacturers,
device_names: filters.deviceNames
}
filters: bodyFilters
})
}

Expand Down Expand Up @@ -1081,13 +1105,16 @@ export const fetchFiltersFromServer = async (selectedApp: typeof emptyApp, filte

let url = `${origin}/apps/${selectedApp.id}/filters`

// fetch the user defined attributes
url += '?ud_attr_keys=1'

// if filter is for Crashes or Anrs, we append a query param indicating it
if (filtersApiType === FiltersApiType.Crash) {
url += '?crash=1'
url += '&crash=1'
} else if (filtersApiType === FiltersApiType.Anr) {
url += '?anr=1'
url += '&anr=1'
} else if (filtersApiType === FiltersApiType.Span) {
url += '?span=1'
url += '&span=1'
}

try {
Expand Down
2 changes: 1 addition & 1 deletion frontend/dashboard/app/components/dropdown_select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const DropdownSelect: React.FC<DropdownSelectProps> = ({ title, type, items, ini
const groupSelectButtonStyle = "text-white text-xs font-display rounded-md border border-white p-1 bg-neutral-950 hover:text-black hover:bg-yellow-200 hover:border-black focus-visible:bg-yellow-200 focus-visible:text-black focus-visible:border-black active:bg-yellow-300 outline-none"
const checkboxContainerStyle = "px-2 py-2 bg-neutral-950 truncate text-white font-display text-left outline-none hover:text-black hover:bg-yellow-200 focus:text-black focus:bg-yellow-200 active:bg-yellow-300"
const checkboxInputStyle = "appearance-none pointer-events-none border-white rounded-sm font-display bg-neutral-950 checked:bg-neutral-950 checked:hover:bg-neutral-950 checked:focus:bg-neutral-950 focus:ring-offset-yellow-200 focus:ring-0 checked:ring-1 checked:ring-white"
const searchInputStyle = "w-full bg-neutral-950 text-white text-sm border border-white rounded-md focus-visible:ring-yellow-300 py-2 px-4 font-sans placeholder:text-gray-400"
const searchInputStyle = "w-full bg-neutral-950 text-white text-sm border border-white rounded-md py-2 px-4 font-sans placeholder:text-gray-400 focus:outline-none focus:border-yellow-300 focus:ring-1 focus:ring-yellow-300"

return (
<div className="relative inline-block text-left select-none" ref={dropdownRef} >
Expand Down
1 change: 1 addition & 0 deletions frontend/dashboard/app/components/exceptions_details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const ExceptionsDetails: React.FC<ExceptionsDetailsProps> = ({ exceptions
showLocales={true}
showDeviceManufacturers={true}
showDeviceNames={true}
showUdAttrs={true}
showFreeText={false}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />

Expand Down
1 change: 1 addition & 0 deletions frontend/dashboard/app/components/exceptions_overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const ExceptionsOverview: React.FC<ExceptionsOverviewProps> = ({ exceptio
showLocales={true}
showDeviceManufacturers={true}
showDeviceNames={true}
showUdAttrs={true}
showFreeText={false}
onFiltersChanged={(updatedFilters) => setFilters(updatedFilters)} />
<div className="py-4" />
Expand Down
21 changes: 19 additions & 2 deletions frontend/dashboard/app/components/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import { useRouter } from "next/navigation";
import { formatDateToHumanReadableDateTime, formatIsoDateForDateTimeInputField, isValidTimestamp } from "../utils/time_utils";
import { useEffect, useState } from "react";
import { AppVersion, AppsApiStatus, FiltersApiStatus, FiltersApiType, OsVersion, SessionType, RootSpanNamesApiStatus, emptyApp, fetchAppsFromServer, fetchFiltersFromServer, fetchRootSpanNamesFromServer, SpanStatus } from "../api/api_calls";
import { AppVersion, AppsApiStatus, FiltersApiStatus, FiltersApiType, OsVersion, SessionType, RootSpanNamesApiStatus, emptyApp, fetchAppsFromServer, fetchFiltersFromServer, fetchRootSpanNamesFromServer, SpanStatus, UserDefAttr } from "../api/api_calls";
import { DateTime } from "luxon";
import DropdownSelect, { DropdownSelectType } from "./dropdown_select";
import FilterPill from "./filter_pill";
import CreateApp from "./create_app";
import DebounceTextInput from "./debounce_text_input";
import LoadingSpinner from "./loading_spinner";
import UserDefAttrSelector, { UdAttrMatcher } from "./user_def_attr_selector";

export enum AppVersionsInitialSelectionType {
Latest,
Expand All @@ -36,6 +37,7 @@ interface FiltersProps {
showLocales: boolean
showDeviceManufacturers: boolean
showDeviceNames: boolean
showUdAttrs: boolean
showFreeText: boolean
onFiltersChanged: (filters: Filters) => void
}
Expand Down Expand Up @@ -74,6 +76,7 @@ export type Filters = {
locales: string[]
deviceManufacturers: string[]
deviceNames: string[]
udAttrMatchers: UdAttrMatcher[]
freeText: string
}

Expand Down Expand Up @@ -101,6 +104,7 @@ export const defaultFilters: Filters = {
locales: [],
deviceManufacturers: [],
deviceNames: [],
udAttrMatchers: [],
freeText: ''
}

Expand Down Expand Up @@ -136,6 +140,7 @@ const Filters: React.FC<FiltersProps> = ({
showLocales,
showDeviceManufacturers,
showDeviceNames,
showUdAttrs,
showFreeText,
onFiltersChanged }) => {

Expand Down Expand Up @@ -220,6 +225,10 @@ const Filters: React.FC<FiltersProps> = ({
const [deviceNames, setDeviceNames] = useState([] as string[]);
const [selectedDeviceNames, setSelectedDeviceNames] = useState([] as string[]);

const [userDefAttrs, setUserDefAttrs] = useState([] as UserDefAttr[]);
const [userDefAttrOps, setUserDefAttrOps] = useState<Map<string, string[]>>(new Map());
const [selectedUdAttrMatchers, setSelectedUdAttrMatchers] = useState<UdAttrMatcher[]>([]);

const [selectedFreeText, setSelectedFreeText] = useState('');

const [selectedDateRange, setSelectedDateRange] = useState(persistedFilters === null ? DateRange.Last6Hours : persistedFilters.dateRange)
Expand Down Expand Up @@ -404,6 +413,11 @@ const Filters: React.FC<FiltersProps> = ({
setSelectedDeviceNames(result.data.device_names)
}

if (result.data.ud_attrs !== null && result.data.ud_attrs.key_types !== null && result.data.ud_attrs.operator_types !== null) {
setUserDefAttrs(result.data.ud_attrs.key_types)
setUserDefAttrOps(new Map(Object.entries(result.data.ud_attrs.operator_types)))
}

break
}
}
Expand Down Expand Up @@ -458,12 +472,13 @@ const Filters: React.FC<FiltersProps> = ({
locales: selectedLocales,
deviceManufacturers: selectedDeviceManufacturers,
deviceNames: selectedDeviceNames,
udAttrMatchers: selectedUdAttrMatchers,
freeText: selectedFreeText
}

sessionStorage.setItem(persistedFiltersStorageKey, JSON.stringify(updatedPersistedFilters))
onFiltersChanged(updatedSelectedFilters)
}, [filtersApiStatus, selectedStartDate, selectedEndDate, selectedVersions, selectedSessionType, selectedOsVersions, selectedCountries, selectedNetworkProviders, selectedNetworkTypes, selectedNetworkGenerations, selectedLocales, selectedDeviceManufacturers, selectedDeviceNames, selectedFreeText, selectedRootSpanName, selectedSpanStatuses])
}, [filtersApiStatus, selectedStartDate, selectedEndDate, selectedVersions, selectedSessionType, selectedOsVersions, selectedCountries, selectedNetworkProviders, selectedNetworkTypes, selectedNetworkGenerations, selectedLocales, selectedDeviceManufacturers, selectedDeviceNames, selectedUdAttrMatchers, selectedFreeText, selectedRootSpanName, selectedSpanStatuses])

return (
<div>
Expand Down Expand Up @@ -547,6 +562,7 @@ const Filters: React.FC<FiltersProps> = ({
{showLocales && locales.length > 0 && <DropdownSelect type={DropdownSelectType.MultiString} title="Locale" items={locales} initialSelected={selectedLocales} onChangeSelected={(items) => setSelectedLocales(items as string[])} />}
{showDeviceManufacturers && deviceManufacturers.length > 0 && <DropdownSelect type={DropdownSelectType.MultiString} title="Device Manufacturer" items={deviceManufacturers} initialSelected={selectedDeviceManufacturers} onChangeSelected={(items) => setSelectedDeviceManufacturers(items as string[])} />}
{showDeviceNames && deviceNames.length > 0 && <DropdownSelect type={DropdownSelectType.MultiString} title="Device Name" items={deviceNames} initialSelected={selectedDeviceNames} onChangeSelected={(items) => setSelectedDeviceNames(items as string[])} />}
{showUdAttrs && userDefAttrs.length > 0 && <UserDefAttrSelector attrs={userDefAttrs} ops={userDefAttrOps} onChangeSelected={(udAttrMatchers) => setSelectedUdAttrMatchers(udAttrMatchers)} />}
{showFreeText && <DebounceTextInput id="free-text" placeholder="Search User/Session ID, Logs, Event Type, Target View ID, File/Class name or Exception Traces..." initialValue={selectedFreeText} onChange={(input) => setSelectedFreeText(input)} />}
</div>
<div className="py-4" />
Expand All @@ -564,6 +580,7 @@ const Filters: React.FC<FiltersProps> = ({
{showLocales && selectedLocales.length > 0 && <FilterPill title={Array.from(selectedLocales).join(', ')} />}
{showDeviceManufacturers && selectedDeviceManufacturers.length > 0 && <FilterPill title={Array.from(selectedDeviceManufacturers).join(', ')} />}
{showDeviceNames && selectedDeviceNames.length > 0 && <FilterPill title={Array.from(selectedDeviceNames).join(', ')} />}
{showUdAttrs && selectedUdAttrMatchers.length > 0 && <FilterPill title={selectedUdAttrMatchers.map(matcher => `${matcher.key} (${matcher.type}) ${matcher.op} ${matcher.value}`).join(', ')} />}
{showFreeText && selectedFreeText !== '' && <FilterPill title={"Search Text: " + selectedFreeText} />}
</div>
</div>
Expand Down
Loading

0 comments on commit 6b759c4

Please sign in to comment.