Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): implement ud attr filters #1739

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading