Skip to content

Commit

Permalink
feat: add basic datav-observability datasource #290
Browse files Browse the repository at this point in the history
  • Loading branch information
sunface committed Oct 17, 2023
1 parent b58e1b0 commit 6226336
Show file tree
Hide file tree
Showing 25 changed files with 524 additions and 23 deletions.
10 changes: 9 additions & 1 deletion query/datav.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@ plugins:
## As datav has a example demo panel for explain how to develop external plugins
## but it is no use to you, so we default disable it here
disable_panels: ["demo"]
disable_datasources: []
disable_datasources: []


#################################### Observability ##############################
observability:
## when enabled, datav-observability features will be toggle on
## you can collect metrics, traces and logs into Datav, and view them through observability plugins
## please visit https://datav.io/docs/observability for more info
enable: true
1 change: 1 addition & 0 deletions query/internal/plugins/external/clickhouse/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (p *ClickHousePlugin) Query(c *gin.Context, ds *models.Datasource) models.P
v[i] = v1.Unix()
}
}

data = append(data, v)
}

Expand Down
3 changes: 3 additions & 0 deletions query/internal/uiConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type UIConfig struct {
Sidemenu *models.SideMenu `json:"sidemenu"`

Plugins *Plugins `json:"plugins"`

Observability *config.Observability `json:"observability"`
}

type Plugins struct {
Expand Down Expand Up @@ -78,6 +80,7 @@ func getUIConfig(c *gin.Context) {
EnableGithubLogin: config.Data.User.EnableGithubLogin,
GithubOAuthToken: config.Data.User.GithubOAuthToken,
Plugins: (*Plugins)(&config.Data.Plugins),
Observability: &config.Data.Observability,
}

// query sidemenu
Expand Down
5 changes: 5 additions & 0 deletions query/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ type Config struct {
DisablePanels []string `yaml:"disable_panels"`
DisableDatasources []string `yaml:"disable_datasources"`
}
Observability
}

type Observability struct {
Enable bool `yaml:"enable" json:"enable"`
}

// Data ...
Expand Down
14 changes: 10 additions & 4 deletions ui/src/components/datasource/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import { chakraComponents } from "chakra-react-select"
import { Variant } from "chakra-react-select/dist/types/types"
import InputSelect from "src/components/select/InputSelect"
import React from "react"
import { DatasourceType } from "types/dashboard"
import { useStore } from "@nanostores/react"
import { $datasources } from "src/views/datasource/store"
import { $teams } from "src/views/team/store"
import { externalDatasourcePlugins } from "src/views/dashboard/plugins/external/plugins"
import { builtinDatasourcePlugins } from "src/views/dashboard/plugins/built-in/plugins"
import { isPluginDisabled } from "utils/plugins"

interface Props {
value: number
onChange: any
allowTypes?: DatasourceType[]
allowTypes?: string[]
variant?: Variant
size?: "sm" | "md" | "lg"
}
Expand All @@ -37,8 +38,13 @@ const DatasourceSelect = ({ value, onChange, allowTypes = [], variant = "unstyle
if (allowTypes.length > 0 && !allowTypes.includes(ds.type)) {
return
}

const p0 = builtinDatasourcePlugins[ds.type]
const p = externalDatasourcePlugins[ds.type]
const plugin = p0??p
if (isPluginDisabled(plugin)) {
return
}

options.push({
label: ds.name,
value: ds.id,
Expand All @@ -47,7 +53,7 @@ const DatasourceSelect = ({ value, onChange, allowTypes = [], variant = "unstyle
annotation: teams.find(t => ds.teamId == t.id)?.name,
})
})


return (
<InputSelect width="100%" isClearable value={value?.toString()} label={datasources.find(ds => ds.id == value)?.name} placeholder={"select datasource, support variable"} size="md" options={options} onChange={onChange} variant="unstyled" enableInput />
Expand Down
6 changes: 3 additions & 3 deletions ui/src/components/form/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ interface Props {
onLabelClick?: any
}

const FormItem = ({ title, children, labelWidth = "fit-content", desc = null, px = 3, colorSchema="gray" ,size="md",spacing=2,alignItems="top",onLabelClick,...rest }:Props & StyleProps) => {
const FormItem = ({ title, children, labelWidth = "fit-content", desc = null, px = 3, colorSchema="gray" ,size="md",spacing=2,alignItems="top",onLabelClick,flexDirection="row",...rest }:Props & StyleProps) => {
return (
<HStack alignItems={alignItems} spacing={spacing} {...rest}>
<Flex alignItems={alignItems} gap={spacing} flexDirection={flexDirection} {...rest}>
<HStack pos="relative" alignItems="center" height={`${size=="md" ? 'var(--chakra-sizes-10)' : (size=="sm" ? 'var(--chakra-sizes-8)' : 'var(--chakra-sizes-12)')}`} px={px} minWidth="fit-content" className={colorSchema == "gray" ? "label-bg" : "tag-bg"} fontSize={size=="lg" ? "1rem" : "0.9rem"} borderRadius="1px">
{typeof title == "string" ? <Text width={labelWidth} className="form-item-label" onClick={onLabelClick} cursor={onLabelClick && "pointer"}>{title}</Text> :<Box width={labelWidth} className="form-item-label">{title}</Box>}
{desc && <Tooltip label={desc}><Box position={"absolute" } right="2"><IoMdInformationCircleOutline /></Box></Tooltip>}
</HStack>
{children}
</HStack>
</Flex>
)
}

Expand Down
5 changes: 3 additions & 2 deletions ui/src/components/select/InputSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ interface SelectProps {
matchWidth?: boolean
enableInput?: boolean
width?: string
annotationDir?: "horizontal" | "vertical"
}

const InputSelect = ({ value,label, options, onChange, variant = "outline", customOption = null, placeholder = "...", size = "sm", isClearable = false, placement="bottom", showArrow = true,closeOnBlur=true ,matchWidth=true,enableInput=true,width="200px"}: SelectProps) => {
const InputSelect = ({ value,label, options, onChange, variant = "outline", customOption = null, placeholder = "...", size = "sm", isClearable = false, placement="bottom", showArrow = true,closeOnBlur=true ,matchWidth=true,enableInput=true,width="200px",annotationDir="horizontal"}: SelectProps) => {
const { isOpen, onToggle, onClose } = useDisclosure()
const [entered, setEntered] = useState(false)
const [query, setQuery] = useState('')
Expand Down Expand Up @@ -109,7 +110,7 @@ const InputSelect = ({ value,label, options, onChange, variant = "outline", cust
{
searchedResult.map(option => <HStack key={option.label} className="hover-bg" py="1" px="2" cursor="pointer" onClick={() => onOptionClick(option)} fontSize="0.9rem">
<Box color="brand.500" fontSize="0.6rem" width="12px">{value == option.value && <FaCheck />}</Box>
{customOption ? customOption(option) : <Flex justifyContent="space-between" width="100%">
{customOption ? customOption(option) : <Flex flexDirection={annotationDir == "horizontal" ? "row" : "column"} justifyContent="space-between" width="100%">
<HStack alignItems="end">
<Text>{option.label}</Text>
{!isEmpty(option.subLabel) && <Text textStyle="annotation">{option.subLabel}</Text>}
Expand Down
8 changes: 7 additions & 1 deletion ui/src/data/configs/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ const config: UIConfig = {
showAlertIcon: false,
githubOAuthToken: '',
enableGithubLogin: false,
sidemenu: null
sidemenu: null,
observability: {
enable: false
}
};

export const $config = atom<UIConfig>(config)
Expand All @@ -52,4 +55,7 @@ export interface UIConfig {
disablePanels : string[]
disableDatasources: string[]
}
observability?: {
enable: boolean
}
}
2 changes: 1 addition & 1 deletion ui/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@
"legendFormat": "支持变量",
"selecMetrics": "选择 metrics...",
"expandTimeline": "显示完整时间轴",
"expandTimelineDesc": "当开启后,所有的时间点都将显示,即时该时间点没有任何数据,对于观察图表在哪里缺失了数据很有帮助"
"expandTimelineDesc": "当开启后,所有的时间点都将显示,即使该时间点没有任何数据,对于观察图表在哪里缺失了数据很有帮助"
},
"httpDs": {
"remoteHttp": "远程 HTTP 地址",
Expand Down
1 change: 1 addition & 0 deletions ui/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface DatasourcePluginComponents {
settings: {
type: string;
icon: string;
disabled?: () => boolean
},
}

Expand Down
18 changes: 18 additions & 0 deletions ui/src/utils/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 Datav.io Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

export const isPluginDisabled = (p) => {
if (p && p.settings?.disabled) {
return p.settings.disabled()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2023 Datav.io Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Datasource } from "types/datasource"
import React from "react";
interface Props {
datasource: Datasource
onChange: any
}

const HttpDatasourceEditor = ({datasource, onChange}: Props) => {
return (<></>)
}

export default HttpDatasourceEditor

export const isHttpDatasourceValid = (ds: Datasource) => {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 Datav.io Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HStack, Input, Text, useMediaQuery, VStack } from "@chakra-ui/react"
import { Form } from "src/components/form/Form"
import FormItem from "src/components/form/Item"
import { cloneDeep } from "lodash"
import { useEffect, useState } from "react"
import { PanelQuery } from "types/dashboard"
import { DatasourceEditorProps } from "types/datasource"
import React from "react";
import { useStore } from "@nanostores/react"
import { PanelTypeAlert } from "../../panel/alert/types"
import { locale } from "src/i18n/i18n"
import InputSelect from "components/select/InputSelect"
import { MobileVerticalBreakpoint } from "src/data/constants"

const HttpQueryEditor = ({ panel, datasource, query, onChange }: DatasourceEditorProps) => {
const code = useStore(locale)
const [tempQuery, setTempQuery] = useState<PanelQuery>(cloneDeep(query))
const apiDesc = apiList.find(api => api.name == tempQuery.metrics)?.desc
const [isMobileScreen] = useMediaQuery(MobileVerticalBreakpoint)
return (<>
<Form spacing={1}>
<FormItem title="API" labelWidth="100px" size="sm" alignItems={isMobileScreen ? "start" : "center"} flexDirection={isMobileScreen ? "column" : "row"}>
<InputSelect value={tempQuery.metrics} options={apiList.map(api => ({ label: api.name, value: api.name, annotation: api.desc }))} annotationDir="vertical" onChange={v => {
const q = { ...tempQuery, metrics: v }
setTempQuery(q)
onChange(q)
}} />
{!isMobileScreen && apiDesc && <Text textStyle="annotation">{apiDesc}</Text>}
</FormItem>
</Form>
</>)
}

export default HttpQueryEditor



const apiList = [{
name: "getServiceInfoList",
desc: "get service infos, such as p99 latency, errors, qps, render as a table",
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2023 Datav.io Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CodeEditorModal } from "src/components/CodeEditor/CodeEditorModal"
import Label from "src/components/form/Item"
import { isEmpty } from "lodash"
import { DatasourceVariableEditorProps } from "types/datasource"
import { isJSON } from "utils/is"
import { useEffect } from "react"
import { queryHttpVariableValues } from "./query_runner"
import FormItem from "src/components/form/Item"
import { EditorInputItem } from "src/components/editor/EditorItem"
import React from "react";
import { useStore } from "@nanostores/react"
import { httpDsMsg } from "src/i18n/locales/en"

const HttpVariableEditor = ({ variable, onChange, onQueryResult }: DatasourceVariableEditorProps) => {
const t1 = useStore(httpDsMsg)
const data = isJSON(variable.value) ? JSON.parse(variable.value) : {}

let update;
if (isEmpty(data.transformResult)) {
data.transformResult = initTransformResult
update = true
}
if (isEmpty(data.transformRequest)) {
data.transformRequest = initTransformRequest
update = true
}
if (update) onChange(variable => {
variable.value = JSON.stringify(data)
})

useEffect(() => {
loadVariables(variable)
}, [variable])

const loadVariables = async (v) => {
const result = await queryHttpVariableValues(variable)
onQueryResult(result)
}

return (<>
<FormItem title="URL">
<EditorInputItem
value={data.url}
onChange={(v) => {
data.url = v
onChange(variable => {
variable.value = JSON.stringify(data)
})
}}
placeholder="support variable"
/>
</FormItem>
<FormItem title={t1.reqTransform}>
{/* <Label width="200px" desc="If you want insert some imformation before request is sent to remote, e.g current time, just edit this function">Request transform</Label> */}
<CodeEditorModal value={data.transformRequest} onChange={v => {
data.transformRequest = v
onChange(variable => {
variable.value = JSON.stringify(data)
})
}} />
</FormItem>


<FormItem title={t1.respTransform}>
{/* <Label width="200px" desc="The http request result is probably not compatible with your visualization panels, here you can define a function to transform the result">Result transform</Label> */}
<CodeEditorModal value={data.transformResult} onChange={v => {
data.transformResult = v
onChange(variable => {
variable.value = JSON.stringify(data)
})
}} />
</FormItem>
</>)
}

export default HttpVariableEditor



const initTransformRequest =
`
// support variables
function transformRequest(url,headers,startTime, endTime, variables) {
let newUrl = url + \`?&start=$\{startTime}&end=$\{endTime}\`
return newUrl
}`

const initTransformResult =
`function transformResult(httpResult) {
console.log("here333 transformResult:", httpResult)
return httpResult
}`
Loading

0 comments on commit 6226336

Please sign in to comment.