Skip to content

Commit

Permalink
feat: add options provider for tags input in trace panel #290
Browse files Browse the repository at this point in the history
  • Loading branch information
sunface committed Nov 4, 2023
1 parent 1302fe8 commit 9e73aef
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 30 deletions.
19 changes: 15 additions & 4 deletions ui/src/views/dashboard/plugins/built-in/panel/datavTrace/Trace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
// limitations under the License.

import React, { useMemo } from "react";
import { Box, Center, HStack, useMediaQuery } from "@chakra-ui/react"
import { Box, Center, HStack, Text, useMediaQuery } from "@chakra-ui/react"
import { PanelProps } from "types/dashboard"
import TraceSearchPanel from "./components/SearchPanel"
import { memo, useEffect, useState } from "react"
import { Trace } from "src/views/dashboard/plugins/built-in/panel/trace/types/trace"

import { cloneDeep } from "lodash";
import { cloneDeep, set } from "lodash";
import { replaceWithVariables } from "utils/variable";
import { getNewestTimeRange } from "src/components/DatePicker/TimePicker";
import { getDatasource } from "utils/datasource";
Expand Down Expand Up @@ -75,6 +75,7 @@ const TracePanel = (props: PanelProps) => {
const [traces, setTraces] = useState<Trace[]>(null)
const [traceChart, setTraceChart] = useState<QueryPluginData>(null)
const [traceTagKeys, setTraceTagKeys] = useState<TraceTagKey[]>([])
const [error, setError] = useState<string>(null)
const datasources = useStore($datasources)
const ds = getDatasource(props.panel.datasource.id, datasources)
const dsPlugin = builtinDatasourcePlugins[ds.type] ?? externalDatasourcePlugins[ds.type]
Expand Down Expand Up @@ -118,6 +119,15 @@ const TracePanel = (props: PanelProps) => {
query.interval = intervalObj.intervalMs / 1000

const res = await dsPlugin.runQuery(panel, query, props.timeRange, ds, { tags, min, max, limit, service: services, operation: operations, aggregate, groupby, onlyChart })
console.log("here33333:",res)
if (res.error) {
setError(res.error)
setTraces(null)
setTraceChart(null)
return
} else {
setError(null)
}
if (!onlyChart) {
setTraces(transformTraces(res.data.traces))
}
Expand Down Expand Up @@ -150,7 +160,7 @@ const TracePanel = (props: PanelProps) => {
const groupByOptions = useMemo(() => {
const groupByOptions = [{label: "None", value: ""},{ label: "operationName", value: "name" },{ label: "serviceName", value: "serviceName" }]
for (const tagKey of traceTagKeys) {
if (tagKey.name != "serviceName" && tagKey.name != "name") {
if (tagKey.name != "serviceName" && tagKey.name != "name" && tagKey.isColumn) {
groupByOptions.push({ label: tagKey.name, value: tagKey.name })
}
}
Expand All @@ -162,12 +172,13 @@ const TracePanel = (props: PanelProps) => {
<HStack alignItems="top" px="2" py="1" spacing={isLargeScreen ? 6 : 2}>
<Box width={searchPanelWidth} pt="2" pl="1" maxH={props.height} px={isLargeScreen ? 4 : 0}>
<CustomScrollbar>
<TraceSearchPanel timeRange={props.timeRange} dashboardId={props.dashboardId} panel={props.panel} onSearch={onSearch} onSearchIds={onSearchIds} />
<TraceSearchPanel timeRange={props.timeRange} dashboardId={props.dashboardId} panel={props.panel} onSearch={onSearch} onSearchIds={onSearchIds} traceTagKeys={traceTagKeys}/>
</CustomScrollbar>
</Box>
<Box width={`calc(100% - ${searchPanelWidth})`} maxH={resultHeight}>
<CustomScrollbar>
{traces && panel.plugins[PanelType].chart && <TraceSearchResult traces={traces} panel={props.panel} dashboardId={props.dashboardId} teamId={props.teamId} timeRange={props.timeRange} height={resultHeight} traceChart={traceChart} traceChartOptions={panel.plugins[PanelType].chart} groupByOptions={groupByOptions} />}
{error && <Text>{error}</Text>}
</CustomScrollbar>
</Box>
</HStack>}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Form, FormSection } from "src/components/form/Form"
import InputSelect from "src/components/select/InputSelect"
import { useEffect, useMemo, useState } from "react"
import { Panel } from "types/dashboard"
import { Panel } from "types/dashboard"
import { queryJaegerOperations, queryJaegerServices } from "../../../datasource/jaeger/query_runner"
import { isEqual, set, sortBy, uniq } from "lodash"
import { EditorInputItem, EditorNumberItem } from "src/components/editor/EditorItem"
import { Button, Checkbox, Divider, Flex, HStack, Input, Text, useMediaQuery } from "@chakra-ui/react"
import { Box, Button, Checkbox, Divider, Flex, HStack, Input, Popover, PopoverArrow, PopoverBody, PopoverContent, PopoverTrigger, Portal, Text, useMediaQuery, VStack } from "@chakra-ui/react"
import storage from "utils/localStorage"
import { AutoComplete } from 'antd';

import { TimeRange } from "types/time"
import { hasVariableFormat, replaceWithVariablesHasMultiValues } from "utils/variable"
Expand All @@ -27,25 +28,28 @@ import { DatasourceTypeJaeger } from "../../../datasource/jaeger/types"
import { PanelType } from "../types"
import { TraceSearchKey } from "../../trace/config/constants"
import { useSearchParam } from "react-use"
import { TraceTagKey } from "../Trace"
import { FaList } from "react-icons/fa"
interface Props {
panel: Panel
onSearch: any
onSearchIds: any
dashboardId: string
timeRange: TimeRange
traceTagKeys?: TraceTagKey[]
}

// cache services parsed by variables, when a variable changes, we can compare the new parsed result with cache
// and decide whether need to re-query operations
const traceServicesCache = new Map()
const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds }: Props) => {
const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds, traceTagKeys }: Props) => {
const [searchParams] = useSearchParams()
const variables = useStore($variables)
const t1 = useStore(tracePanelMsg)
const [inited, setInited] = useState(false)
const [services, setServices] = useState([])
const [operations, setOperations] = useState([])

const [searchProvider, setSearchProvider] = useState<string>("")
const lastSearch = useMemo(() => storage.get(TraceSearchKey + dashboardId + panel.id) ?? {}, [])
const [initService, initOperation, initTags, initMax, initMin, initLimit] = getInitParams(searchParams, panel, lastSearch)
const aggregate = useSearchParam("aggregate")
Expand All @@ -64,23 +68,23 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
const [useLatestTime, setUseLatestTime] = useState(true)
const datasources = useStore($datasources)
const ds = getDatasource(panel.datasource.id, datasources)

useEffect(() => {
if (inited) {
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby],true])
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], true])
}
},[aggregate, groupby])
}, [aggregate, groupby])

useEffect(() => {
if (!isEmpty(initTraceIds)) {
onSearchIds(initTraceIds)
return
return
}

if (service) {
loadOperations()
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby],false])
}
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], false])
}

setTimeout(() => {
setInited(true)
Expand All @@ -99,7 +103,7 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
onSearchIds(traceIds)
} else {
if (service && operation) {
onSearch(service, operation, tags, min, max, limit,useLatestTime, [[aggregate, groupby],false])
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], false])
}
}
}
Expand All @@ -113,12 +117,12 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
if (inited) {
if (!isEmpty(traceIds)) {
onSearchIds(traceIds)
return
return
}

if (service) {
loadOperations()
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby],false])
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], false])
} else {
setOperations([])
}
Expand All @@ -129,10 +133,10 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
if (inited) {
setTraceIds(initTraceIds)
}
},[initTraceIds])
}, [initTraceIds])
useEffect(() => {
if (!inited) {
return
return
}
if (hasVariableFormat(operation)) {
return
Expand All @@ -141,7 +145,7 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
if (!hasVariableFormat(service)) {
return
}

const services = replaceWithVariablesHasMultiValues(service)
const cachedServices = traceServicesCache[dashboardId + panel.id]
if (!isEqual(services, cachedServices)) {
Expand All @@ -153,9 +157,9 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
loadOperations()
if (!isEmpty(traceIds)) {
onSearchIds(traceIds)
return
return
}
onSearch(service, operation, tags, min, max, limit, useLatestTime,[[aggregate, groupby],false])
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], false])
}

useBus(
Expand All @@ -181,7 +185,7 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
case DatasourceTypeJaeger:
const res = await queryJaegerServices(panel.datasource.id)
const ss = sortBy(res)
setServices(ss)
setServices(ss)
setTimeout(() => {
setInited(true)
if (ss.length > 0) {
Expand Down Expand Up @@ -220,24 +224,49 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
break;
}
}

const onClickSearch = () => {
if (!isEmpty(traceIds)) {
onSearchIds(traceIds)
} else {
onSearch(service, operation, tags, min, max, limit, useLatestTime,[[aggregate, groupby],false])
onSearch(service, operation, tags, min, max, limit, useLatestTime, [[aggregate, groupby], false])
storage.set(TraceSearchKey + dashboardId + panel.id, {
service, operation, tags, min, max, limit
})
}
}

const tagsProvider = useMemo(() => {
if (!traceTagKeys) {
return null
}

const provider = []
for (const tag of traceTagKeys) {
let v: string
if (tag.isColumn) {
v = tag.name

} else if (tag.dataType == "resource") {
v = "resources." + tag.name
} else {
v = "attributes." + tag.name
}

if (v.toLowerCase().includes(searchProvider.toLowerCase())) {
provider.push(v)
}

}

return provider
}, [traceTagKeys, searchProvider])

const [isLargeScreen] = useMediaQuery(MobileBreakpoint)
const size = "sm"
return (<>
<Form spacing={isLargeScreen ? 4 : 2} fontSize={isLargeScreen ? '0.8rem' : "0.7rem"}>
<FormSection title="Service" spacing={1}>
<FormSection title="Service" spacing={1}>
{
panel.plugins[PanelType].enableEditService ?
<InputSelect variant="flushed" value={service} options={services.map(s => ({ label: s, value: s }))} size={size} onChange={v => setService(v)} />
Expand All @@ -248,13 +277,31 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
<InputSelect variant="flushed" value={operation} options={operations.map(s => ({ label: s, value: s }))} size={size} onChange={v => setOperation(v)} />
</FormSection>
<FormSection title="Tags" spacing={1}>
<EditorInputItem bordered={false} borderedBottom value={tags} placeholder={`e.g http.status_code=200 error=true`} onChange={v => setTags(v)} />
<HStack>
<EditorInputItem key={tags} bordered={false} borderedBottom value={tags} placeholder={`e.g http.status_code=200 error=true`} onChange={v => setTags(v)} />
{tagsProvider && <Popover trigger="click" placement="right" >
<PopoverTrigger>
<Box><FaList /></Box>
</PopoverTrigger>
<Portal >
<PopoverContent>
<PopoverArrow />
<PopoverBody>
<Input value={searchProvider} onChange={e => setSearchProvider(e.currentTarget.value)} placeholder="search options..."/>
<VStack pt="2" alignItems={"left"} h="calc(100vh - 50px)" overflowY="auto">
{tagsProvider.map(t => <Text cursor="pointer" className="hover-text" onClick={() => setTags(tags + " " + t + "=")}>{t}</Text>)}
</VStack>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>}
</HStack>
</FormSection>
<Flex flexDir={isLargeScreen ? "row" : "column"} gap={1}>
<FormSection title={t1.minDuration} spacing={1}>
<FormSection title={t1.minDuration} spacing={1}>
<EditorInputItem bordered={false} borderedBottom value={min} placeholder="e.g 1.2s,100ms" onChange={v => setMin(v)} />
</FormSection>
<FormSection title={t1.maxDuration} spacing={1}>
<FormSection title={t1.maxDuration} spacing={1}>
<EditorInputItem bordered={false} borderedBottom value={max} placeholder="e.g 1.2s,100ms" onChange={v => setMax(v)} />
</FormSection>
</Flex>
Expand All @@ -263,7 +310,7 @@ const TraceSearchPanel = ({ timeRange, dashboardId, panel, onSearch, onSearchIds
</FormSection>
<Divider pt="2" />
<FormSection title="Trace ids" spacing={1} desc={t1.traceIdsTips}>
<EditorInputItem key={traceIds} placeholder={t1.traceIdsInputTips} value={traceIds} onChange={v => {setTraceIds(v); addParamToUrl({traceIds: v})}} size="md" />
<EditorInputItem key={traceIds} placeholder={t1.traceIdsInputTips} value={traceIds} onChange={v => { setTraceIds(v); addParamToUrl({ traceIds: v }) }} size="md" />
</FormSection>
<Flex flexDir={isLargeScreen ? "row" : "column"} justifyContent="space-between" gap={3} pt="2">
<Button variant="outline" width={isLargeScreen ? "120px" : null} size={size} onClick={onClickSearch}>{isLargeScreen ? t1.findTraces : "Search"}</Button>
Expand Down

0 comments on commit 9e73aef

Please sign in to comment.