Skip to content

Commit

Permalink
add a warning for WITH templates in uncreated dashboards #123 (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
Loori-R authored Dec 18, 2023
1 parent 654cc80 commit 6c9b726
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 81 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* FEATURE: add Windows support for backend plugin. See how to build backend plugin for various platforms [here](https://github.com/VictoriaMetrics/grafana-datasource#3-how-to-build-backend-plugin). See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/67).
* FEATURE: migrate to React to prevent warnings about the discontinuation of Angular support. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/102).
* FEATURE: add `--version` flag for backend datasource binary. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/68).
* FEATURE: add a warning window about using `WITH templates` for not yet created dashboards. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/123).
* FEATURE: add a separate scope for storing `WITH templates` for the Explore tab.

* BUGFIX: fix incorrect parsing when switching between code/builder modes in query editor. See [this issue](https://github.com/VictoriaMetrics/grafana-datasource/issues/112)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { FC } from "react";

import { Alert, Button, HorizontalGroup } from "@grafana/ui";

type Props = {
onClose: () => void;
onAccept: () => void
}

const WarningNewDashboard: FC<Props> = ({ onClose, onAccept }) => (
<Alert
title="Please save dashboard before using WITH templates"
severity="error"
elevated
>
<div>
This dashboard is not saved yet. Please save it before using WITH templates.
<p>Otherwise, the templates will not be saved</p>
</div>
<HorizontalGroup justify="flex-end" spacing="xs">
<Button fill={"text"} onClick={onClose}>
Cancel
</Button>
<Button variant={"destructive"} fill={"text"} onClick={onAccept}>
Proceed with WITH templates
</Button>
</HorizontalGroup>
</Alert>
)

export default WarningNewDashboard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { FC, useCallback, useEffect, useState } from 'react'

import { Badge, Button, useStyles2 } from "@grafana/ui";

import TemplateEditor from "../TemplateEditor/TemplateEditor";
import useUpdateDatasource from "../hooks/useUpdateDatasource";
import useValidateExpr from "../hooks/useValidateExpr";
import { WithTemplateConfigProps } from "../index";

import getStyles from "./style";

interface Props extends WithTemplateConfigProps {
handleClose: () => void;
}

const WithTemplateBody: FC<Props> = ({ datasource, dashboardUID, template, setTemplate, handleClose }) => {
const styles = useStyles2(getStyles);

const { validateResult, isValidExpr } = useValidateExpr(datasource.id)
const { updateDatasource } = useUpdateDatasource({
datasourceUID: datasource.uid,
dashboardUID
})

const [value, setValue] = useState(template?.expr || "")
const [isLoading, setIsLoading] = useState(false)

const handleSave = useCallback(async () => {
setIsLoading(true)
const isValid = await isValidExpr(value)
if (!isValid) {
setIsLoading(false)
return
}
try {
const templates = await updateDatasource(value)
datasource.withTemplatesUpdate(templates)
setTemplate(templates.find(t => t?.uid === dashboardUID))
handleClose()
} catch (e) {
console.error(e)
}
setIsLoading(false)
}, [value, isValidExpr, updateDatasource, datasource, dashboardUID, setTemplate, handleClose])

useEffect(() => {
setTemplate(datasource.withTemplates.find(t => t.uid === dashboardUID))
}, [setTemplate, datasource, dashboardUID])

useEffect(() => {
value && isValidExpr(value)
}, [value, isValidExpr])

useEffect(() => {
setValue(template?.expr || "")
}, [template])

return (
<div className={styles.body}>
<TemplateEditor
initialValue={value}
datasource={datasource}
onChange={setValue}
/>
<Badge
icon={validateResult.icon || "info"}
color={validateResult.color || "blue"}
text={validateResult.error || validateResult.title}
/>
<div className={styles.button}>
<a
className="text-link"
target="_blank"
href={"https://github.com/VictoriaMetrics/grafana-datasource#how-to-use-with-templates"}
rel="noreferrer"
>
<Button
variant={'secondary'}
fill={"text"}
icon={"book"}
size={"sm"}
>
How it works?
</Button>
</a>
<Button
variant={'success'}
onClick={handleSave}
disabled={isLoading}
>
Save
</Button>
</div>
</div>
)
}

export default WithTemplateBody;
File renamed without changes.
117 changes: 37 additions & 80 deletions src/components/WithTemplateConfig/index.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,62 @@
import React, { FC, useCallback, useEffect, useState } from 'react'
import React, { FC, useEffect, useMemo, useState } from 'react'

import { Badge, Button, IconButton, Modal, useStyles2 } from "@grafana/ui";
import { CoreApp } from "@grafana/data";
import { IconButton, Modal } from "@grafana/ui";

import { PrometheusDatasource } from "../../datasource";

import TemplateEditor from "./TemplateEditor/TemplateEditor";
import WarningNewDashboard from "./WarningSavedDashboard/WarningSavedDashboard";
import WithTemplateBody from "./WithTemplateBody/WithTemplateBody";
import getDashboardByUID from "./api/getDashboardList";
import useUpdateDatasource from "./hooks/useUpdateDatasource";
import useValidateExpr from "./hooks/useValidateExpr";
import getStyles from "./style";
import { DashboardResponse, WithTemplate } from "./types";

interface Props {
export interface WithTemplateConfigProps {
template?: WithTemplate;
setTemplate: React.Dispatch<React.SetStateAction<WithTemplate | undefined>>
datasource: PrometheusDatasource;
dashboardUID: string;
app?: CoreApp;
}

const WithTemplateConfig: FC<Props> = ({ template, setTemplate, dashboardUID, datasource }) => {
const styles = useStyles2(getStyles);
const WithTemplateConfig: FC<WithTemplateConfigProps> = ({ template, setTemplate, dashboardUID, datasource, app }) => {
const [isValidDashboard, setIsValidDashboard] = useState(app === CoreApp.Explore)

const { validateResult, isValidExpr } = useValidateExpr(datasource.id)
const { updateDatasource } = useUpdateDatasource({
datasourceUID: datasource.uid,
dashboardUID
})

const [value, setValue] = useState(template?.expr || "")
const [dashboardResponse, setDashboardResponse] = useState<DashboardResponse | null>()
const [isLoading, setIsLoading] = useState(false)

const dashboardTitle = `${dashboardResponse?.dashboard?.title || "current"}`
const dashboardFolder = `${dashboardResponse?.meta?.folderTitle || "General"}`
const modalTitle = useMemo(() => {
const explore = app === CoreApp.Explore ? "Explore" : ""
const folderTitle = dashboardResponse?.meta?.folderTitle
const dashboardTitle = dashboardResponse?.dashboard?.title
const templatesTitle = "WITH templates"
const fullTitle = [explore, folderTitle, dashboardTitle, templatesTitle].filter(Boolean).join(" / ")
return isValidDashboard ? fullTitle : templatesTitle
}, [isValidDashboard, dashboardResponse, app])

const [showTemplates, setShowTemplates] = useState(false);
const handleClose = () => setShowTemplates(false);
const handleOpen = () => setShowTemplates(true);

const handleSave = useCallback(async () => {
setIsLoading(true)
const isValid = await isValidExpr(value)
if (!isValid) {
setIsLoading(false)
return
}
try {
const templates = await updateDatasource(value)
datasource.withTemplatesUpdate(templates)
setTemplate(templates.find(t => t?.uid === dashboardUID))
handleClose()
} catch (e) {
console.error(e)
}
setIsLoading(false)
}, [value, isValidExpr, updateDatasource, datasource, dashboardUID, setTemplate])
const handleAcceptWarning = () => setIsValidDashboard(true)

useEffect(() => {
setTemplate(datasource.withTemplates.find(t => t.uid === dashboardUID))
}, [setTemplate, datasource, dashboardUID])

useEffect(() => {
value && isValidExpr(value)
}, [value, isValidExpr])

useEffect(() => {
setValue(template?.expr || "")
}, [template])

useEffect(() => {
const fetchDashboard = async () => {
try {
const dashboardResponse = await getDashboardByUID(dashboardUID)
setDashboardResponse(dashboardResponse)
setIsValidDashboard(true)
} catch (e) {
console.error(e)
}
}
if (dashboardUID) {fetchDashboard()}
}, [dashboardUID]);
if (dashboardUID) {
fetchDashboard()
} else if (app !== CoreApp.Explore) {
setIsValidDashboard(false)
}
}, [dashboardUID, app]);

return (
<>
Expand All @@ -89,47 +67,26 @@ const WithTemplateConfig: FC<Props> = ({ template, setTemplate, dashboardUID, da
onClick={handleOpen}
/>
<Modal
title={`${dashboardFolder} / ${dashboardTitle} / WITH templates`}
title={modalTitle}
isOpen={showTemplates}
closeOnEscape={true}
closeOnBackdropClick={false}
onDismiss={handleClose}
>
<div className={styles.body}>
<TemplateEditor
initialValue={value}
{isValidDashboard ? (
<WithTemplateBody
datasource={datasource}
onChange={setValue}
dashboardUID={dashboardUID || app || ""}
handleClose={handleClose}
template={template}
setTemplate={setTemplate}
/>
<Badge
icon={validateResult.icon || "info"}
color={validateResult.color || "blue"}
text={validateResult.error || validateResult.title}
) : (
<WarningNewDashboard
onAccept={handleAcceptWarning}
onClose={handleClose}
/>
<div className={styles.button}>
<a
className="text-link"
target="_blank"
href={"https://github.com/VictoriaMetrics/grafana-datasource#how-to-use-with-templates"} rel="noreferrer"
>
<Button
variant={'secondary'}
fill={"text"}
icon={"book"}
size={"sm"}
>
How it works?
</Button>
</a>
<Button
variant={'success'}
onClick={handleSave}
disabled={isLoading}
>
Save
</Button>
</div>
</div>
)}
</Modal>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion src/datasource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ export class PrometheusDatasource
expr = this.enhanceExprWithAdHocFilters(expr);

// Apply WITH templates
const dashboardUID = options.dashboardUID
const dashboardUID = options.dashboardUID || options.app || ""
expr = mergeTemplateWithQuery(expr, this.withTemplates.find(t => t.uid === dashboardUID))

// Only replace vars in expression after having (possibly) updated interval vars
Expand Down
1 change: 1 addition & 0 deletions src/querybuilder/components/PromQueryEditorSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const PromQueryEditorSelector = React.memo<Props>((props) => {
<QueryHeaderSwitch label="Raw" value={rawQuery} onChange={onShowRawChange}/>
<FlexItem grow={1}/>
<WithTemplateConfig
app={app}
template={templateByDashboard}
setTemplate={setTemplateByDashboard}
dashboardUID={dashboardUID}
Expand Down

0 comments on commit 6c9b726

Please sign in to comment.