Handle deletion of a tempVar used in nested tempVar

Catch error and send notification of error
pull/10616/head
Iris Scholten 2018-07-09 15:17:35 -07:00
parent 234023dd5e
commit 4156c42684
5 changed files with 98 additions and 36 deletions

View File

@ -1,6 +1,7 @@
import {replace, RouterAction} from 'react-router-redux' import {replace, RouterAction} from 'react-router-redux'
import _ from 'lodash' import _ from 'lodash'
import qs from 'qs' import qs from 'qs'
import {Dispatch} from 'redux'
import { import {
getDashboards as getDashboardsAJAX, getDashboards as getDashboardsAJAX,
@ -38,6 +39,7 @@ import {
notifyDashboardNotFound, notifyDashboardNotFound,
notifyInvalidZoomedTimeRangeValueInURLQuery, notifyInvalidZoomedTimeRangeValueInURLQuery,
notifyInvalidTimeRangeValueInURLQuery, notifyInvalidTimeRangeValueInURLQuery,
notifyInvalidTempVarValueInMetaQuery,
} from 'src/shared/copy/notifications' } from 'src/shared/copy/notifications'
import {getDeep} from 'src/utils/wrappers' import {getDeep} from 'src/utils/wrappers'
@ -396,7 +398,7 @@ const getDashboard = (state, dashboardId: number): Dashboard => {
// Thunkers // Thunkers
export const getDashboardsAsync = () => async ( export const getDashboardsAsync = () => async (
dispatch dispatch: Dispatch<Action>
): Promise<Dashboard[]> => { ): Promise<Dashboard[]> => {
try { try {
const { const {
@ -442,7 +444,7 @@ const removeUnselectedTemplateValues = (dashboard: Dashboard): Template[] => {
} }
export const putDashboard = (dashboard: Dashboard) => async ( export const putDashboard = (dashboard: Dashboard) => async (
dispatch dispatch: Dispatch<Action>
): Promise<void> => { ): Promise<void> => {
try { try {
// save only selected template values to server // save only selected template values to server
@ -469,7 +471,7 @@ export const putDashboard = (dashboard: Dashboard) => async (
} }
export const putDashboardByID = (dashboardID: number) => async ( export const putDashboardByID = (dashboardID: number) => async (
dispatch, dispatch: Dispatch<Action>,
getState getState
): Promise<void> => { ): Promise<void> => {
try { try {
@ -483,7 +485,7 @@ export const putDashboardByID = (dashboardID: number) => async (
} }
export const updateDashboardCell = (dashboard: Dashboard, cell: Cell) => async ( export const updateDashboardCell = (dashboard: Dashboard, cell: Cell) => async (
dispatch dispatch: Dispatch<Action>
): Promise<void> => { ): Promise<void> => {
try { try {
const {data} = await updateDashboardCellAJAX(cell) const {data} = await updateDashboardCellAJAX(cell)
@ -495,7 +497,7 @@ export const updateDashboardCell = (dashboard: Dashboard, cell: Cell) => async (
} }
export const deleteDashboardAsync = (dashboard: Dashboard) => async ( export const deleteDashboardAsync = (dashboard: Dashboard) => async (
dispatch dispatch: Dispatch<Action>
): Promise<void> => { ): Promise<void> => {
dispatch(deleteDashboard(dashboard)) dispatch(deleteDashboard(dashboard))
try { try {
@ -515,7 +517,7 @@ export const deleteDashboardAsync = (dashboard: Dashboard) => async (
export const addDashboardCellAsync = ( export const addDashboardCellAsync = (
dashboard: Dashboard, dashboard: Dashboard,
cellType?: CellType cellType?: CellType
) => async (dispatch): Promise<void> => { ) => async (dispatch: Dispatch<Action>): Promise<void> => {
try { try {
const {data} = await addDashboardCellAJAX( const {data} = await addDashboardCellAJAX(
dashboard, dashboard,
@ -532,7 +534,7 @@ export const addDashboardCellAsync = (
export const cloneDashboardCellAsync = ( export const cloneDashboardCellAsync = (
dashboard: Dashboard, dashboard: Dashboard,
cell: Cell cell: Cell
) => async (dispatch): Promise<void> => { ) => async (dispatch: Dispatch<Action>): Promise<void> => {
try { try {
const clonedCell = getClonedDashboardCell(dashboard, cell) const clonedCell = getClonedDashboardCell(dashboard, cell)
const {data} = await addDashboardCellAJAX(dashboard, clonedCell) const {data} = await addDashboardCellAJAX(dashboard, clonedCell)
@ -547,7 +549,7 @@ export const cloneDashboardCellAsync = (
export const deleteDashboardCellAsync = ( export const deleteDashboardCellAsync = (
dashboard: Dashboard, dashboard: Dashboard,
cell: Cell cell: Cell
) => async (dispatch): Promise<void> => { ) => async (dispatch: Dispatch<Action>): Promise<void> => {
try { try {
await deleteDashboardCellAJAX(cell) await deleteDashboardCellAJAX(cell)
dispatch(deleteDashboardCell(dashboard, cell)) dispatch(deleteDashboardCell(dashboard, cell))
@ -559,7 +561,7 @@ export const deleteDashboardCellAsync = (
} }
export const importDashboardAsync = (dashboard: Dashboard) => async ( export const importDashboardAsync = (dashboard: Dashboard) => async (
dispatch dispatch: Dispatch<Action>
): Promise<void> => { ): Promise<void> => {
try { try {
// save only selected template values to server // save only selected template values to server
@ -603,7 +605,7 @@ export const importDashboardAsync = (dashboard: Dashboard) => async (
} }
const updateTimeRangeFromQueryParams = (dashboardID: number) => ( const updateTimeRangeFromQueryParams = (dashboardID: number) => (
dispatch, dispatch: Dispatch<Action>,
getState getState
): void => { ): void => {
const {dashTimeV1} = getState() const {dashTimeV1} = getState()
@ -660,6 +662,27 @@ const updateTimeRangeFromQueryParams = (dashboardID: number) => (
dispatch(updateQueryParams(updatedQueryParams)) dispatch(updateQueryParams(updatedQueryParams))
} }
const hydrateTemplates = (
templates: Template[],
nonNestedTemplates: Template[],
proxyLink: string,
dispatch: Dispatch<Action>
) => {
return templates.map(async t => {
try {
return await hydrateTemplate(proxyLink, t, nonNestedTemplates)
} catch (error) {
const errorMessage = getDeep(error, 'data.message', '')
.replace(/.*(err):/g, '')
.trim()
dispatch(
notify(notifyInvalidTempVarValueInMetaQuery(t.tempVar, errorMessage))
)
return t
}
})
}
export const getDashboardWithTemplatesAsync = ( export const getDashboardWithTemplatesAsync = (
dashboardId: number, dashboardId: number,
source: Source source: Source
@ -679,17 +702,23 @@ export const getDashboardWithTemplatesAsync = (
const templateSelections = templateSelectionsFromQueryParams() const templateSelections = templateSelectionsFromQueryParams()
const proxyLink = source.links.proxy const proxyLink = source.links.proxy
const nonNestedTemplates = await Promise.all( const nonNestedTemplates = await Promise.all(
dashboard.templates hydrateTemplates(
.filter(t => !isTemplateNested(t)) dashboard.templates.filter(t => !isTemplateNested(t)),
.map(t => hydrateTemplate(proxyLink, t, [])) [],
proxyLink,
dispatch
)
) )
applyLocalSelections(nonNestedTemplates, templateSelections) applyLocalSelections(nonNestedTemplates, templateSelections)
const nestedTemplates = await Promise.all( const nestedTemplates = await Promise.all(
dashboard.templates hydrateTemplates(
.filter(t => isTemplateNested(t)) dashboard.templates.filter(t => isTemplateNested(t)),
.map(t => hydrateTemplate(proxyLink, t, nonNestedTemplates)) nonNestedTemplates,
proxyLink,
dispatch
)
) )
applyLocalSelections(nestedTemplates, templateSelections) applyLocalSelections(nestedTemplates, templateSelections)
@ -722,7 +751,7 @@ export const rehydrateNestedTemplatesAsync = (
} }
export const updateTemplateQueryParams = (dashboardId: number) => ( export const updateTemplateQueryParams = (dashboardId: number) => (
dispatch, dispatch: Dispatch<Action>,
getState getState
): void => { ): void => {
const templates = getDashboard(getState(), dashboardId).templates const templates = getDashboard(getState(), dashboardId).templates

View File

@ -168,7 +168,20 @@ class DashboardPage extends Component<Props, State> {
const prevPath = getDeep(prevProps.location, 'pathname', null) const prevPath = getDeep(prevProps.location, 'pathname', null)
const thisPath = getDeep(this.props.location, 'pathname', null) const thisPath = getDeep(this.props.location, 'pathname', null)
if (prevPath && thisPath && prevPath !== thisPath) { const templates = getDeep<TempVarsModels.Template[]>(
this.props.dashboard,
'templates',
[]
).map(t => t.tempVar)
const prevTemplates = getDeep<TempVarsModels.Template[]>(
prevProps.dashboard,
'templates',
[]
).map(t => t.tempVar)
const isTemplateDeleted: boolean =
_.intersection(templates, prevTemplates).length !== prevTemplates.length
if ((prevPath && thisPath && prevPath !== thisPath) || isTemplateDeleted) {
this.getDashboard() this.getDashboard()
} }
} }

View File

@ -526,6 +526,16 @@ export const notifyBuilderDisabled = (): Notification => ({
// Template Variables & URL Queries // Template Variables & URL Queries
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyInvalidTempVarValueInMetaQuery = (
tempVar: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
icon: 'cube',
duration: 7500,
message: `Invalid query supplied for template variable ${tempVar}: ${errorMessage}`,
})
export const notifyInvalidTempVarValueInURLQuery = ({ export const notifyInvalidTempVarValueInURLQuery = ({
key, key,
value, value,

View File

@ -16,28 +16,35 @@ export const hydrateTemplate = async (
return template return template
} }
const query = templateReplace(makeQueryForTemplate(template.query), templates) try {
const response = await proxy({source: proxyLink, query}) const query = templateReplace(
const values = parseMetaQuery(query, response.data) makeQueryForTemplate(template.query),
const type = TEMPLATE_VARIABLE_TYPES[template.type] templates
const selectedValue = getSelectedValue(template) )
const selectedLocalValue = getLocalSelectedValue(template) const response = await proxy({source: proxyLink, query})
const values = parseMetaQuery(query, response.data)
const type = TEMPLATE_VARIABLE_TYPES[template.type]
const selectedValue = getSelectedValue(template)
const selectedLocalValue = getLocalSelectedValue(template)
const templateValues = values.map(value => { const templateValues = values.map(value => {
return { return {
type, type,
value, value,
selected: value === selectedValue, selected: value === selectedValue,
localSelected: value === selectedLocalValue, localSelected: value === selectedLocalValue,
}
})
if (templateValues.length && !templateValues.find(v => v.selected)) {
// Handle stale selected value
templateValues[0].selected = true
} }
})
if (templateValues.length && !templateValues.find(v => v.selected)) { return {...template, values: templateValues}
// Handle stale selected value } catch (error) {
templateValues[0].selected = true throw error
} }
return {...template, values: templateValues}
} }
export const isTemplateNested = (template: Template): boolean => { export const isTemplateNested = (template: Template): boolean => {

View File

@ -1,6 +1,8 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import {ErrorHandling} from 'src/shared/decorators/errors'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown' import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology' import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor' import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
@ -22,6 +24,7 @@ interface State {
isAdding: boolean isAdding: boolean
} }
@ErrorHandling
class TemplateControlBar extends Component<Props, State> { class TemplateControlBar extends Component<Props, State> {
constructor(props) { constructor(props) {
super(props) super(props)