Support nested template variables
parent
f5ca544edf
commit
983550cb9e
|
@ -46,6 +46,7 @@
|
|||
"@types/node": "^9.4.6",
|
||||
"@types/papaparse": "^4.1.34",
|
||||
"@types/prop-types": "^15.5.2",
|
||||
"@types/qs": "^6.5.1",
|
||||
"@types/react": "^16.0.38",
|
||||
"@types/react-dnd": "^2.0.36",
|
||||
"@types/react-dnd-html5-backend": "^2.1.9",
|
||||
|
@ -143,7 +144,7 @@
|
|||
"nano-date": "^2.0.1",
|
||||
"papaparse": "^4.4.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"query-string": "^5.0.0",
|
||||
"qs": "^6.5.2",
|
||||
"react": "^16.3.1",
|
||||
"react-addons-shallow-compare": "^15.0.2",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import {bindActionCreators} from 'redux'
|
||||
import {replace} from 'react-router-redux'
|
||||
import {replace, RouterAction} from 'react-router-redux'
|
||||
import _ from 'lodash'
|
||||
import queryString from 'query-string'
|
||||
|
||||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import {parseMetaQuery} from 'src/tempVars/utils/parsing'
|
||||
import qs from 'qs'
|
||||
|
||||
import {
|
||||
getDashboards as getDashboardsAJAX,
|
||||
|
@ -17,13 +13,15 @@ import {
|
|||
createDashboard as createDashboardAJAX,
|
||||
} from 'src/dashboards/apis'
|
||||
import {getMe} from 'src/shared/apis/auth'
|
||||
import {hydrateTemplate, isTemplateNested} from 'src/tempVars/apis'
|
||||
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
import {errorThrown} from 'src/shared/actions/errors'
|
||||
|
||||
import {
|
||||
generateURLQueryParamsFromTempVars,
|
||||
findInvalidTempVarsInURLQuery,
|
||||
applySelections,
|
||||
templateSelectionsFromQueryParams,
|
||||
queryParamsFromTemplates,
|
||||
} from 'src/dashboards/utils/tempVars'
|
||||
import {validTimeRange, validAbsoluteTimeRange} from 'src/dashboards/utils/time'
|
||||
import {
|
||||
|
@ -38,12 +36,10 @@ import {
|
|||
notifyDashboardImportFailed,
|
||||
notifyDashboardImported,
|
||||
notifyDashboardNotFound,
|
||||
notifyInvalidTempVarValueInURLQuery,
|
||||
notifyInvalidZoomedTimeRangeValueInURLQuery,
|
||||
notifyInvalidTimeRangeValueInURLQuery,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
|
||||
|
@ -52,11 +48,7 @@ import {defaultTimeRange} from 'src/shared/data/timeRanges'
|
|||
|
||||
// Types
|
||||
import {Dispatch} from 'redux'
|
||||
import {InjectedRouter} from 'react-router'
|
||||
import {Location} from 'history'
|
||||
import {AxiosResponse} from 'axios'
|
||||
import {LocationAction} from 'react-router-redux'
|
||||
import * as AuthReducers from 'src/types/reducers/auth'
|
||||
import * as DashboardsActions from 'src/types/actions/dashboards'
|
||||
import * as DashboardsApis from 'src/types/apis/dashboards'
|
||||
import * as DashboardsModels from 'src/types/dashboards'
|
||||
|
@ -216,28 +208,11 @@ export const templateVariableLocalSelected: DashboardsActions.TemplateVariableLo
|
|||
},
|
||||
})
|
||||
|
||||
export const templateVariablesLocalSelectedByName: DashboardsActions.TemplateVariablesLocalSelectedByNameActionCreator = (
|
||||
dashboardID: number,
|
||||
queryParams: TempVarsModels.URLQueryParams
|
||||
): DashboardsActions.TemplateVariablesLocalSelectedByNameAction => ({
|
||||
type: 'TEMPLATE_VARIABLES_SELECTED_BY_NAME',
|
||||
payload: {
|
||||
dashboardID,
|
||||
queryParams,
|
||||
},
|
||||
})
|
||||
|
||||
export const editTemplateVariableValues: DashboardsActions.EditTemplateVariableValuesActionCreator = (
|
||||
dashboardID: number,
|
||||
templateID: string,
|
||||
values
|
||||
): DashboardsActions.EditTemplateVariableValuesAction => ({
|
||||
type: 'EDIT_TEMPLATE_VARIABLE_VALUES',
|
||||
payload: {
|
||||
dashboardID,
|
||||
templateID,
|
||||
values,
|
||||
},
|
||||
export const updateTemplates = (
|
||||
templates: TempVarsModels.Template[]
|
||||
): DashboardsActions.UpdateTemplatesAction => ({
|
||||
type: 'UPDATE_TEMPLATES',
|
||||
payload: {templates},
|
||||
})
|
||||
|
||||
export const setHoverTime: DashboardsActions.SetHoverTimeActionCreator = (
|
||||
|
@ -258,6 +233,21 @@ export const setActiveCell: DashboardsActions.SetActiveCellActionCreator = (
|
|||
},
|
||||
})
|
||||
|
||||
const getDashboard = (
|
||||
state,
|
||||
dashboardId: number
|
||||
): DashboardsModels.Dashboard => {
|
||||
const dashboard = state.dashboardUI.dashboards.find(
|
||||
d => d.id === +dashboardId
|
||||
)
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error(`Could not find dashboard with id '${dashboardId}'`)
|
||||
}
|
||||
|
||||
return dashboard
|
||||
}
|
||||
|
||||
// Async Action Creators
|
||||
|
||||
export const getDashboardsAsync: DashboardsActions.GetDashboardsDispatcher = (): DashboardsActions.GetDashboardsThunk => async (
|
||||
|
@ -280,20 +270,6 @@ export const getDashboardsAsync: DashboardsActions.GetDashboardsDispatcher = ():
|
|||
}
|
||||
}
|
||||
|
||||
export const getDashboardAsync = (dashboardID: number) => async (
|
||||
dispatch
|
||||
): Promise<DashboardsModels.Dashboard | null> => {
|
||||
try {
|
||||
const {data: dashboard} = await getDashboardAJAX(dashboardID)
|
||||
dispatch(loadDashboard(dashboard))
|
||||
return dashboard
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(errorThrown(error))
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const getChronografVersion = () => async (): Promise<string | void> => {
|
||||
try {
|
||||
const results = await getMe()
|
||||
|
@ -366,12 +342,7 @@ export const putDashboardByID: DashboardsActions.PutDashboardByIDDispatcher = (
|
|||
getState: () => DashboardsReducers.Dashboards
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const {
|
||||
dashboardUI: {dashboards},
|
||||
} = getState()
|
||||
const dashboard: DashboardsModels.Dashboard = dashboards.find(
|
||||
d => d.id === +dashboardID
|
||||
)
|
||||
const dashboard = getDashboard(getState(), dashboardID)
|
||||
const templates = removeUnselectedTemplateValues(dashboard)
|
||||
await updateDashboardAJAX({...dashboard, templates})
|
||||
} catch (error) {
|
||||
|
@ -531,147 +502,27 @@ export const importDashboardAsync = (
|
|||
}
|
||||
}
|
||||
|
||||
export const hydrateTempVarValuesAsync = (
|
||||
dashboardID: number,
|
||||
source: SourcesModels.Source
|
||||
) => async (dispatch, getState): Promise<void> => {
|
||||
try {
|
||||
const dashboard = getState().dashboardUI.dashboards.find(
|
||||
d => d.id === dashboardID
|
||||
)
|
||||
const templates: TempVarsModels.Template[] = dashboard.templates
|
||||
const queries = templates
|
||||
.filter(
|
||||
template => getDeep<string>(template, 'query.influxql', '') !== ''
|
||||
)
|
||||
.map(async template => {
|
||||
const query = makeQueryForTemplate(template.query)
|
||||
const response = await proxy({source: source.links.proxy, query})
|
||||
const values = parseMetaQuery(query, response.data)
|
||||
|
||||
return {template, values}
|
||||
})
|
||||
const results = await Promise.all(queries)
|
||||
|
||||
for (const {template, values} of results) {
|
||||
dispatch(editTemplateVariableValues(+dashboard.id, template.id, values))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(errorThrown(error))
|
||||
}
|
||||
}
|
||||
|
||||
const removeNullValues = obj => _.pickBy(obj, o => o)
|
||||
|
||||
export const syncURLQueryParamsFromQueryParamsObject = (
|
||||
location: Location,
|
||||
updatedURLQueryParams: TempVarsModels.URLQueryParams,
|
||||
deletedURLQueryParams: TempVarsModels.URLQueryParams = {}
|
||||
): DashboardsActions.SyncURLQueryFromQueryParamsObjectActionCreator => (
|
||||
dispatch: Dispatch<LocationAction>
|
||||
const updateTimeRangeFromQueryParams = (dashboardID: number) => (
|
||||
dispatch,
|
||||
getState
|
||||
): void => {
|
||||
const updatedLocationQuery = removeNullValues({
|
||||
...location.query,
|
||||
...updatedURLQueryParams,
|
||||
const {dashTimeV1} = getState()
|
||||
const queryParams = qs.parse(window.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
|
||||
_.each(deletedURLQueryParams, (__, k) => {
|
||||
delete updatedLocationQuery[k]
|
||||
})
|
||||
|
||||
const updatedSearchString = queryString.stringify(updatedLocationQuery)
|
||||
const updatedSearch = {search: updatedSearchString}
|
||||
const updatedLocation = {
|
||||
...location,
|
||||
query: updatedLocationQuery,
|
||||
...updatedSearch,
|
||||
}
|
||||
|
||||
dispatch(replace(updatedLocation))
|
||||
}
|
||||
|
||||
export const syncURLQueryFromTempVars: DashboardsActions.SyncURLQueryFromTempVarsDispatcher = (
|
||||
location: Location,
|
||||
tempVars: TempVarsModels.Template[],
|
||||
deletedTempVars: TempVarsModels.Template[] = [],
|
||||
urlQueryParamsTimeRanges?: TempVarsModels.URLQueryParams
|
||||
): DashboardsActions.SyncURLQueryFromQueryParamsObjectActionCreator => (
|
||||
dispatch: Dispatch<
|
||||
DashboardsActions.SyncURLQueryFromQueryParamsObjectDispatcher
|
||||
>
|
||||
): void => {
|
||||
const updatedURLQueryParams = generateURLQueryParamsFromTempVars(tempVars)
|
||||
const deletedURLQueryParams = generateURLQueryParamsFromTempVars(
|
||||
deletedTempVars
|
||||
)
|
||||
|
||||
let updatedURLQueryParamsWithTimeRange = {
|
||||
...updatedURLQueryParams,
|
||||
}
|
||||
|
||||
if (urlQueryParamsTimeRanges) {
|
||||
updatedURLQueryParamsWithTimeRange = {
|
||||
...updatedURLQueryParamsWithTimeRange,
|
||||
...urlQueryParamsTimeRanges,
|
||||
}
|
||||
}
|
||||
|
||||
syncURLQueryParamsFromQueryParamsObject(
|
||||
location,
|
||||
updatedURLQueryParamsWithTimeRange,
|
||||
deletedURLQueryParams
|
||||
)(dispatch)
|
||||
}
|
||||
|
||||
const syncDashboardTempVarsFromURLQueryParams = (
|
||||
dashboardID: number,
|
||||
urlQueryParams: TempVarsModels.URLQueryParams
|
||||
): DashboardsActions.SyncDashboardTempVarsFromURLQueryParamsDispatcher => (
|
||||
dispatch: Dispatch<
|
||||
| NotificationsActions.PublishNotificationActionCreator
|
||||
| DashboardsActions.TemplateVariableLocalSelectedAction
|
||||
>,
|
||||
getState: () => DashboardsReducers.Dashboards & AuthReducers.Auth
|
||||
): void => {
|
||||
const {dashboardUI} = getState()
|
||||
const dashboard = dashboardUI.dashboards.find(d => d.id === dashboardID)
|
||||
|
||||
const urlQueryParamsTempVarsWithInvalidValues = findInvalidTempVarsInURLQuery(
|
||||
dashboard.templates,
|
||||
urlQueryParams
|
||||
)
|
||||
urlQueryParamsTempVarsWithInvalidValues.forEach(invalidURLQuery => {
|
||||
dispatch(notify(notifyInvalidTempVarValueInURLQuery(invalidURLQuery)))
|
||||
})
|
||||
|
||||
dispatch(templateVariablesLocalSelectedByName(dashboardID, urlQueryParams))
|
||||
}
|
||||
|
||||
const syncDashboardTimeRangeFromURLQueryParams = (
|
||||
dashboardID: number,
|
||||
urlQueryParams: TempVarsModels.URLQueryParams,
|
||||
location: Location
|
||||
): DashboardsActions.SyncDashboardTimeRangeFromURLQueryParamsDispatcher => (
|
||||
dispatch: Dispatch<NotificationsActions.PublishNotificationActionCreator>,
|
||||
getState: () => DashboardsReducers.Dashboards & DashboardsReducers.DashTimeV1
|
||||
): void => {
|
||||
const {
|
||||
dashboardUI: {dashboards},
|
||||
dashTimeV1,
|
||||
} = getState()
|
||||
const dashboard = dashboards.find(d => d.id === dashboardID)
|
||||
|
||||
const timeRangeFromQueries = {
|
||||
lower: urlQueryParams.lower,
|
||||
upper: urlQueryParams.upper,
|
||||
lower: queryParams.lower,
|
||||
upper: queryParams.upper,
|
||||
}
|
||||
|
||||
const zoomedTimeRangeFromQueries = {
|
||||
lower: urlQueryParams.zoomedLower,
|
||||
upper: urlQueryParams.zoomedUpper,
|
||||
lower: queryParams.zoomedLower,
|
||||
upper: queryParams.zoomedUpper,
|
||||
}
|
||||
|
||||
let validatedTimeRange = validTimeRange(timeRangeFromQueries)
|
||||
|
||||
if (!validatedTimeRange.lower) {
|
||||
const dashboardTimeRange = dashTimeV1.ranges.find(
|
||||
r => r.dashboardID === idNormalizer(TYPE_ID, dashboardID)
|
||||
|
@ -683,100 +534,118 @@ const syncDashboardTimeRangeFromURLQueryParams = (
|
|||
dispatch(notify(notifyInvalidTimeRangeValueInURLQuery()))
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setDashTimeV1(dashboardID, validatedTimeRange))
|
||||
|
||||
const validatedZoomedTimeRange = validAbsoluteTimeRange(
|
||||
zoomedTimeRangeFromQueries
|
||||
)
|
||||
|
||||
if (
|
||||
!validatedZoomedTimeRange.lower &&
|
||||
(urlQueryParams.zoomedLower || urlQueryParams.zoomedUpper)
|
||||
(queryParams.zoomedLower || queryParams.zoomedUpper)
|
||||
) {
|
||||
dispatch(notify(notifyInvalidZoomedTimeRangeValueInURLQuery()))
|
||||
}
|
||||
|
||||
dispatch(setZoomedTimeRange(validatedZoomedTimeRange))
|
||||
const urlQueryParamsTimeRanges = {
|
||||
|
||||
const updatedQueryParams = {
|
||||
lower: validatedTimeRange.lower,
|
||||
upper: validatedTimeRange.upper,
|
||||
zoomedLower: validatedZoomedTimeRange.lower,
|
||||
zoomedUpper: validatedZoomedTimeRange.upper,
|
||||
}
|
||||
|
||||
syncURLQueryFromTempVars(
|
||||
location,
|
||||
dashboard.templates,
|
||||
[],
|
||||
urlQueryParamsTimeRanges
|
||||
)(dispatch)
|
||||
dispatch(updateQueryParams(updatedQueryParams))
|
||||
}
|
||||
|
||||
const syncDashboardFromURLQueryParams = (
|
||||
dashboardID: number,
|
||||
location: Location
|
||||
): DashboardsActions.SyncDashboardFromURLQueryParamsDispatcher => (
|
||||
dispatch: Dispatch<
|
||||
| DashboardsActions.SyncDashboardTempVarsFromURLQueryParamsDispatcher
|
||||
| DashboardsActions.SyncDashboardTimeRangeFromURLQueryParamsDispatcher
|
||||
>
|
||||
): void => {
|
||||
const urlQueryParams = queryString.parse(window.location.search)
|
||||
bindActionCreators(syncDashboardTempVarsFromURLQueryParams, dispatch)(
|
||||
dashboardID,
|
||||
urlQueryParams
|
||||
)
|
||||
export const getDashboardWithTemplatesAsync = (
|
||||
dashboardId: number,
|
||||
source: SourcesModels.Source
|
||||
) => async (dispatch: Dispatch<any>): Promise<void> => {
|
||||
let dashboard: DashboardsModels.Dashboard
|
||||
|
||||
bindActionCreators(syncDashboardTimeRangeFromURLQueryParams, dispatch)(
|
||||
dashboardID,
|
||||
urlQueryParams,
|
||||
location
|
||||
)
|
||||
}
|
||||
try {
|
||||
const resp = await getDashboardAJAX(dashboardId)
|
||||
dashboard = resp.data
|
||||
} catch {
|
||||
dispatch(replace(`/sources/${source.id}/dashboards`))
|
||||
dispatch(notify(notifyDashboardNotFound(dashboardId)))
|
||||
|
||||
export const getDashboardWithHydratedAndSyncedTempVarsAsync: DashboardsActions.GetDashboardWithHydratedAndSyncedTempVarsAsyncDispatcher = (
|
||||
dashboardID: number,
|
||||
source: SourcesModels.Source,
|
||||
router: InjectedRouter,
|
||||
location: Location
|
||||
): DashboardsActions.GetDashboardWithHydratedAndSyncedTempVarsAsyncThunk => async (
|
||||
dispatch: Dispatch<NotificationsActions.PublishNotificationActionCreator>
|
||||
): Promise<void> => {
|
||||
const dashboard = await bindActionCreators(getDashboardAsync, dispatch)(
|
||||
dashboardID
|
||||
)
|
||||
if (!dashboard) {
|
||||
router.push(`/sources/${source.id}/dashboards`)
|
||||
dispatch(notify(notifyDashboardNotFound(dashboardID)))
|
||||
return
|
||||
}
|
||||
|
||||
await bindActionCreators(hydrateTempVarValuesAsync, dispatch)(
|
||||
dashboardID,
|
||||
source
|
||||
const templateSelections = templateSelectionsFromQueryParams()
|
||||
const proxyLink = source.links.proxy
|
||||
const nonNestedTemplates = await Promise.all(
|
||||
dashboard.templates
|
||||
.filter(t => !isTemplateNested(t))
|
||||
.map(t => hydrateTemplate(proxyLink, t, []))
|
||||
)
|
||||
|
||||
bindActionCreators(syncDashboardFromURLQueryParams, dispatch)(
|
||||
dashboardID,
|
||||
location
|
||||
applySelections(nonNestedTemplates, templateSelections)
|
||||
|
||||
const nestedTemplates = await Promise.all(
|
||||
dashboard.templates
|
||||
.filter(t => isTemplateNested(t))
|
||||
.map(t => hydrateTemplate(proxyLink, t, nonNestedTemplates))
|
||||
)
|
||||
|
||||
applySelections(nestedTemplates, templateSelections)
|
||||
|
||||
const templates = [...nonNestedTemplates, ...nestedTemplates]
|
||||
|
||||
// TODO: Notify if any of the supplied query params were invalid
|
||||
dispatch(loadDashboard({...dashboard, templates}))
|
||||
dispatch<any>(updateTemplateQueryParams(dashboardId))
|
||||
dispatch<any>(updateTimeRangeFromQueryParams(dashboardId))
|
||||
}
|
||||
|
||||
export const setZoomedTimeRangeAsync: DashboardsActions.SetZoomedTimeRangeDispatcher = (
|
||||
zoomedTimeRange: QueriesModels.TimeRange,
|
||||
location: Location
|
||||
): DashboardsActions.SetZoomedTimeRangeThunk => async (
|
||||
dispatch: Dispatch<
|
||||
| DashboardsActions.SetZoomedTimeRangeActionCreator
|
||||
| DashboardsActions.SyncURLQueryFromQueryParamsObjectDispatcher
|
||||
>
|
||||
): Promise<void> => {
|
||||
dispatch(setZoomedTimeRange(zoomedTimeRange))
|
||||
const urlQueryParamsZoomedTimeRange = {
|
||||
zoomedLower: zoomedTimeRange.lower,
|
||||
zoomedUpper: zoomedTimeRange.upper,
|
||||
export const rehydrateNestedTemplatesAsync = (
|
||||
dashboardId: number,
|
||||
source: SourcesModels.Source
|
||||
) => async (dispatch: Dispatch<any>, getState): Promise<void> => {
|
||||
const dashboard = getDashboard(getState(), dashboardId)
|
||||
const proxyLink = source.links.proxy
|
||||
const templateSelections = templateSelectionsFromQueryParams()
|
||||
const nestedTemplates = await Promise.all(
|
||||
dashboard.templates
|
||||
.filter(t => isTemplateNested(t))
|
||||
.map(t => hydrateTemplate(proxyLink, t, dashboard.templates))
|
||||
)
|
||||
|
||||
applySelections(nestedTemplates, templateSelections)
|
||||
|
||||
dispatch(updateTemplates(nestedTemplates))
|
||||
dispatch<any>(updateTemplateQueryParams(dashboardId))
|
||||
}
|
||||
|
||||
export const updateTemplateQueryParams = (dashboardId: number) => (
|
||||
dispatch,
|
||||
getState
|
||||
): void => {
|
||||
const templates = getDashboard(getState(), dashboardId).templates
|
||||
const updatedQueryParams = {
|
||||
tempVars: queryParamsFromTemplates(templates),
|
||||
}
|
||||
|
||||
syncURLQueryParamsFromQueryParamsObject(
|
||||
location,
|
||||
urlQueryParamsZoomedTimeRange
|
||||
)(dispatch)
|
||||
dispatch(updateQueryParams(updatedQueryParams))
|
||||
}
|
||||
|
||||
export const updateQueryParams = (updatedQueryParams: object): RouterAction => {
|
||||
const {search, pathname} = window.location
|
||||
|
||||
const newQueryParams = _.pickBy(
|
||||
{
|
||||
...qs.parse(search, {ignoreQueryPrefix: true}),
|
||||
...updatedQueryParams,
|
||||
},
|
||||
v => !!v
|
||||
)
|
||||
|
||||
const newSearch = qs.stringify(newQueryParams)
|
||||
const newLocation = {pathname, search: `?${newSearch}`}
|
||||
|
||||
return replace(newLocation)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ import * as notifyActions from 'src/shared/actions/notifications'
|
|||
// Utils
|
||||
import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
|
||||
import {millisecondTimeRange} from 'src/dashboards/utils/time'
|
||||
import {stripTempVar} from 'src/dashboards/utils/tempVars'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {getDashboards as getDashboardsAJAX} from 'src/dashboards/apis'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
|
@ -40,6 +40,9 @@ import {WithRouterProps} from 'react-router'
|
|||
import {ManualRefreshProps} from 'src/shared/components/ManualRefresh'
|
||||
import {Location} from 'history'
|
||||
import {InjectedRouter} from 'react-router'
|
||||
import {AxiosResponse} from 'axios'
|
||||
import {RouterAction} from 'react-router-redux'
|
||||
import {DashboardsResponse} from 'src/types/apis/dashboards'
|
||||
import * as AnnotationsActions from 'src/types/actions/annotations'
|
||||
import * as AppActions from 'src/types/actions/app'
|
||||
import * as CellEditorOverlayActions from 'src/types/actions/cellEditorOverlay'
|
||||
|
@ -54,21 +57,17 @@ import * as NotificationsActions from 'src/types/actions/notifications'
|
|||
|
||||
interface DashboardActions {
|
||||
setDashTimeV1: DashboardsActions.SetDashTimeV1ActionCreator
|
||||
setZoomedTimeRange: DashboardsActions.SetZoomedTimeRangeActionCreator
|
||||
updateDashboard: DashboardsActions.UpdateDashboardActionCreator
|
||||
syncURLQueryParamsFromQueryParamsObject: DashboardsActions.SyncURLQueryFromQueryParamsObjectDispatcher
|
||||
putDashboard: DashboardsActions.PutDashboardDispatcher
|
||||
putDashboardByID: DashboardsActions.PutDashboardByIDDispatcher
|
||||
getDashboardsAsync: DashboardsActions.GetDashboardsDispatcher
|
||||
getDashboardWithHydratedAndSyncedTempVarsAsync: DashboardsActions.GetDashboardWithHydratedAndSyncedTempVarsAsyncDispatcher
|
||||
setTimeRange: DashboardsActions.SetTimeRangeActionCreator
|
||||
addDashboardCellAsync: DashboardsActions.AddDashboardCellDispatcher
|
||||
editCellQueryStatus: DashboardsActions.EditCellQueryStatusActionCreator
|
||||
updateDashboardCell: DashboardsActions.UpdateDashboardCellDispatcher
|
||||
cloneDashboardCellAsync: DashboardsActions.CloneDashboardCellDispatcher
|
||||
deleteDashboardCellAsync: DashboardsActions.DeleteDashboardCellDispatcher
|
||||
templateVariableLocalSelected: DashboardsActions.TemplateVariableLocalSelectedActionCreator
|
||||
syncURLQueryFromTempVars: DashboardsActions.SyncURLQueryFromTempVarsDispatcher
|
||||
setZoomedTimeRangeAsync: DashboardsActions.SetZoomedTimeRangeDispatcher
|
||||
}
|
||||
|
||||
interface Props extends DashboardActions, ManualRefreshProps, WithRouterProps {
|
||||
|
@ -108,6 +107,16 @@ interface Props extends DashboardActions, ManualRefreshProps, WithRouterProps {
|
|||
thresholdsListColors: ColorsModels.ColorNumber[]
|
||||
gaugeColors: ColorsModels.ColorNumber[]
|
||||
lineColors: ColorsModels.ColorString[]
|
||||
getDashboardWithTemplatesAsync: (
|
||||
dashboardId: number,
|
||||
source: SourcesModels.Source
|
||||
) => Promise<void>
|
||||
rehydrateNestedTemplatesAsync: (
|
||||
dashboardId: number,
|
||||
source: SourcesModels.Source
|
||||
) => Promise<void>
|
||||
updateTemplateQueryParams: (dashboardId: number) => void
|
||||
updateQueryParams: (updatedQueryParams: object) => RouterAction
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -115,6 +124,7 @@ interface State {
|
|||
selectedCell: DashboardsModels.Cell | null
|
||||
scrollTop: number
|
||||
windowHeight: number
|
||||
dashboardLinks: DashboardsModels.DashboardSwitcherLink[]
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -129,17 +139,12 @@ class DashboardPage extends Component<Props, State> {
|
|||
selectedCell: null,
|
||||
scrollTop: 0,
|
||||
windowHeight: window.innerHeight,
|
||||
dashboardLinks: [],
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {
|
||||
source,
|
||||
getAnnotationsAsync,
|
||||
timeRange,
|
||||
autoRefresh,
|
||||
getDashboardsAsync,
|
||||
} = this.props
|
||||
const {source, getAnnotationsAsync, timeRange, autoRefresh} = this.props
|
||||
|
||||
const annotationRange = millisecondTimeRange(timeRange)
|
||||
getAnnotationsAsync(source.links.annotations, annotationRange)
|
||||
|
@ -154,10 +159,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
|
||||
await this.getDashboard()
|
||||
|
||||
// We populate all dashboards in the redux store so that we can consume
|
||||
// them in `this.dashboardLinks`. See
|
||||
// https://github.com/influxdata/chronograf/issues/3594
|
||||
getDashboardsAsync()
|
||||
this.getDashboardLinks()
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
|
@ -264,7 +266,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
templatesIncludingDashTime = []
|
||||
}
|
||||
|
||||
const {isEditMode} = this.state
|
||||
const {isEditMode, dashboardLinks} = this.state
|
||||
|
||||
return (
|
||||
<div className="page dashboard-page">
|
||||
|
@ -299,7 +301,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
onSave={this.handleRenameDashboard}
|
||||
onCancel={this.handleCancelEditDashboard}
|
||||
onEditDashboard={this.handleEditDashboard}
|
||||
dashboardLinks={this.dashboardLinks}
|
||||
dashboardLinks={dashboardLinks}
|
||||
activeDashboardLink={this.activeDashboardLink}
|
||||
activeDashboard={dashboard ? dashboard.name : ''}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
|
@ -346,17 +348,10 @@ class DashboardPage extends Component<Props, State> {
|
|||
this.setState({windowHeight: window.innerHeight})
|
||||
}
|
||||
|
||||
private getDashboard = async (): Promise<
|
||||
DashboardsActions.GetDashboardWithHydratedAndSyncedTempVarsAsyncThunk
|
||||
> => {
|
||||
const {dashboardID, source, router, location} = this.props
|
||||
private getDashboard = async () => {
|
||||
const {dashboardID, source, getDashboardWithTemplatesAsync} = this.props
|
||||
|
||||
return await this.props.getDashboardWithHydratedAndSyncedTempVarsAsync(
|
||||
dashboardID,
|
||||
source,
|
||||
router,
|
||||
location
|
||||
)
|
||||
return getDashboardWithTemplatesAsync(dashboardID, source)
|
||||
}
|
||||
|
||||
private inView = (cell: DashboardsModels.Cell): boolean => {
|
||||
|
@ -385,18 +380,18 @@ class DashboardPage extends Component<Props, State> {
|
|||
): void => {
|
||||
const {
|
||||
dashboard,
|
||||
|
||||
getAnnotationsAsync,
|
||||
source,
|
||||
location,
|
||||
setDashTimeV1,
|
||||
updateQueryParams,
|
||||
} = this.props
|
||||
|
||||
this.props.setDashTimeV1(dashboard.id, {
|
||||
setDashTimeV1(dashboard.id, {
|
||||
...timeRange,
|
||||
format: FORMAT_INFLUXQL,
|
||||
})
|
||||
|
||||
this.props.syncURLQueryParamsFromQueryParamsObject(location, {
|
||||
updateQueryParams({
|
||||
lower: timeRange.lower,
|
||||
upper: timeRange.upper,
|
||||
})
|
||||
|
@ -450,38 +445,31 @@ class DashboardPage extends Component<Props, State> {
|
|||
): ((value: TempVarsModels.TemplateValue) => void) => (
|
||||
value: TempVarsModels.TemplateValue
|
||||
): void => {
|
||||
const {dashboard, location} = this.props
|
||||
const {
|
||||
dashboard,
|
||||
source,
|
||||
templateVariableLocalSelected,
|
||||
updateTemplateQueryParams,
|
||||
rehydrateNestedTemplatesAsync,
|
||||
} = this.props
|
||||
|
||||
const currentTempVar = dashboard.templates.find(
|
||||
tempVar => tempVar.id === templateID
|
||||
)
|
||||
const strippedTempVar = stripTempVar(currentTempVar.tempVar)
|
||||
|
||||
const updatedQueryParam = {
|
||||
[strippedTempVar]: value.value,
|
||||
}
|
||||
this.props.syncURLQueryParamsFromQueryParamsObject(
|
||||
location,
|
||||
updatedQueryParam
|
||||
)
|
||||
this.props.templateVariableLocalSelected(dashboard.id, templateID, [value])
|
||||
templateVariableLocalSelected(dashboard.id, templateID, [value])
|
||||
updateTemplateQueryParams(dashboard.id)
|
||||
rehydrateNestedTemplatesAsync(dashboard.id, source)
|
||||
}
|
||||
|
||||
private handleSaveTemplateVariables = async (
|
||||
templates: TempVarsModels.Template[]
|
||||
): Promise<void> => {
|
||||
const {location, dashboard} = this.props
|
||||
const {dashboard, updateTemplateQueryParams} = this.props
|
||||
|
||||
try {
|
||||
await this.props.putDashboard({
|
||||
...dashboard,
|
||||
templates,
|
||||
})
|
||||
const deletedTempVars = dashboard.templates.filter(
|
||||
({tempVar: oldTempVar}) =>
|
||||
!templates.find(({tempVar: newTempVar}) => oldTempVar === newTempVar)
|
||||
)
|
||||
this.props.syncURLQueryFromTempVars(location, templates, deletedTempVars)
|
||||
|
||||
updateTemplateQueryParams(dashboard.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
@ -494,8 +482,14 @@ class DashboardPage extends Component<Props, State> {
|
|||
private handleZoomedTimeRange = (
|
||||
zoomedTimeRange: QueriesModels.TimeRange
|
||||
): void => {
|
||||
const {location} = this.props
|
||||
this.props.setZoomedTimeRangeAsync(zoomedTimeRange, location)
|
||||
const {setZoomedTimeRange, updateQueryParams} = this.props
|
||||
|
||||
setZoomedTimeRange(zoomedTimeRange)
|
||||
|
||||
updateQueryParams({
|
||||
zoomedLower: zoomedTimeRange.lower,
|
||||
zoomedUpper: zoomedTimeRange.upper,
|
||||
})
|
||||
}
|
||||
|
||||
private setScrollTop = (e: MouseEvent<JSX.Element>): void => {
|
||||
|
@ -504,16 +498,26 @@ class DashboardPage extends Component<Props, State> {
|
|||
this.setState({scrollTop: target.scrollTop})
|
||||
}
|
||||
|
||||
private get dashboardLinks(): DashboardsModels.DashboardSwitcherLink[] {
|
||||
const {dashboards, source} = this.props
|
||||
private getDashboardLinks = async (): Promise<void> => {
|
||||
const {source} = this.props
|
||||
|
||||
return dashboards.map(d => {
|
||||
return {
|
||||
key: String(d.id),
|
||||
text: d.name,
|
||||
to: `/sources/${source.id}/dashboards/${d.id}`,
|
||||
}
|
||||
})
|
||||
try {
|
||||
const resp = (await getDashboardsAJAX()) as AxiosResponse<
|
||||
DashboardsResponse
|
||||
>
|
||||
const dashboards = resp.data.dashboards
|
||||
const dashboardLinks = dashboards.map(d => {
|
||||
return {
|
||||
key: String(d.id),
|
||||
text: d.name,
|
||||
to: `/sources/${source.id}/dashboards/${d.id}`,
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({dashboardLinks})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private get activeDashboardLink(): DashboardsModels.DashboardSwitcherLink | null {
|
||||
|
@ -523,7 +527,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
return null
|
||||
}
|
||||
|
||||
const {dashboardLinks} = this
|
||||
const {dashboardLinks} = this.state
|
||||
|
||||
return dashboardLinks.find(link => link.key === String(dashboard.id))
|
||||
}
|
||||
|
@ -584,6 +588,11 @@ const mstp = (state, {params: {dashboardID}}) => {
|
|||
|
||||
const mdtp = {
|
||||
...dashboardActions,
|
||||
getDashboardWithTemplatesAsync:
|
||||
dashboardActions.getDashboardWithTemplatesAsync,
|
||||
rehydrateNestedTemplatesAsync: dashboardActions.rehydrateNestedTemplatesAsync,
|
||||
updateTemplateQueryParams: dashboardActions.updateTemplateQueryParams,
|
||||
updateQueryParams: dashboardActions.updateQueryParams,
|
||||
handleChooseAutoRefresh: appActions.setAutoRefresh,
|
||||
templateControlBarVisibilityToggled:
|
||||
appActions.templateControlBarVisibilityToggled,
|
||||
|
|
|
@ -2,8 +2,6 @@ import _ from 'lodash'
|
|||
import {timeRanges} from 'src/shared/data/timeRanges'
|
||||
import {NULL_HOVER_TIME} from 'src/shared/constants/tableGraph'
|
||||
|
||||
import {applyDashboardTempVarOverrides} from 'src/dashboards/utils/tempVars'
|
||||
|
||||
const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h')
|
||||
|
||||
export const initialState = {
|
||||
|
@ -16,8 +14,6 @@ export const initialState = {
|
|||
activeCellID: '',
|
||||
}
|
||||
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/tempVars/constants'
|
||||
|
||||
const ui = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'LOAD_DASHBOARDS': {
|
||||
|
@ -175,61 +171,26 @@ const ui = (state = initialState, action) => {
|
|||
return {...state, dashboards: newDashboards}
|
||||
}
|
||||
|
||||
case 'TEMPLATE_VARIABLES_SELECTED_BY_NAME': {
|
||||
const {dashboardID, queryParams} = action.payload
|
||||
const newDashboards = state.dashboards.map(
|
||||
oldDashboard =>
|
||||
oldDashboard.id === dashboardID
|
||||
? applyDashboardTempVarOverrides(oldDashboard, queryParams)
|
||||
: oldDashboard
|
||||
)
|
||||
|
||||
return {...state, dashboards: newDashboards}
|
||||
}
|
||||
|
||||
case 'EDIT_TEMPLATE_VARIABLE_VALUES': {
|
||||
const {dashboardID, templateID, values} = action.payload
|
||||
case 'UPDATE_TEMPLATES': {
|
||||
const {templates: updatedTemplates} = action.payload
|
||||
|
||||
const dashboards = state.dashboards.map(dashboard => {
|
||||
if (dashboard.id !== dashboardID) {
|
||||
return dashboard
|
||||
}
|
||||
const templates = dashboard.templates.map(template => {
|
||||
if (template.id !== templateID) {
|
||||
return template
|
||||
}
|
||||
const localSelectedValue = _.get(template, 'values', []).find(
|
||||
v => v.localSelected
|
||||
)
|
||||
const selectedValue = _.get(template, 'values', []).find(
|
||||
v => v.selected
|
||||
)
|
||||
const templates = dashboard.templates.reduce(
|
||||
(acc, existingTemplate) => {
|
||||
const updatedTemplate = updatedTemplates.find(
|
||||
t => t.id === existingTemplate.id
|
||||
)
|
||||
|
||||
const newValues = values.map(value => {
|
||||
const isLocalSelected =
|
||||
_.get(localSelectedValue, 'value', null) === value
|
||||
const isSelected = _.get(selectedValue, 'value', null) === value
|
||||
|
||||
const newValue = {
|
||||
localSelected: localSelectedValue ? isLocalSelected : isSelected,
|
||||
selected: isSelected,
|
||||
value,
|
||||
type: TEMPLATE_VARIABLE_TYPES[template.type],
|
||||
if (updatedTemplate) {
|
||||
return [...acc, updatedTemplate]
|
||||
}
|
||||
|
||||
return newValue
|
||||
})
|
||||
return [...acc, existingTemplate]
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
...template,
|
||||
values: newValues,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...dashboard,
|
||||
templates,
|
||||
}
|
||||
return {...dashboard, templates}
|
||||
})
|
||||
|
||||
return {...state, dashboards}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import _ from 'lodash'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import qs from 'qs'
|
||||
|
||||
import {
|
||||
Dashboard,
|
||||
Template,
|
||||
TemplateQuery,
|
||||
TemplateValue,
|
||||
URLQueryParams,
|
||||
} from 'src/types'
|
||||
import {TemplateUpdate} from 'src/types'
|
||||
import {formatTempVar} from 'src/tempVars/utils'
|
||||
|
||||
import {Template, TemplateQuery} from 'src/types'
|
||||
import {TemplateQPSelections} from 'src/types/dashboards'
|
||||
|
||||
export const makeQueryForTemplate = ({
|
||||
influxql,
|
||||
|
@ -21,124 +17,58 @@ export const makeQueryForTemplate = ({
|
|||
.replace(':measurement:', `"${measurement}"`)
|
||||
.replace(':tagKey:', `"${tagKey}"`)
|
||||
|
||||
export const templateSelectionsFromQueryParams = (): TemplateQPSelections => {
|
||||
const queryParams = qs.parse(window.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
const tempVars = queryParams.tempVars || {}
|
||||
|
||||
return Object.entries(tempVars).reduce(
|
||||
(acc, [tempVar, v]) => ({...acc, [formatTempVar(tempVar)]: v}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
export const queryParamsFromTemplates = (templates: Template[]) => {
|
||||
return templates.reduce((acc, template) => {
|
||||
const tempVar = stripTempVar(template.tempVar)
|
||||
const selection = template.values.find(t => t.localSelected)
|
||||
|
||||
if (!selection) {
|
||||
return acc
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[tempVar]: selection.value,
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
export const applySelections = (
|
||||
templates: Template[],
|
||||
selections: TemplateQPSelections
|
||||
): void => {
|
||||
for (const {tempVar, values} of templates) {
|
||||
if (!values.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
let selection = selections[tempVar]
|
||||
|
||||
if (!selection || !values.find(v => v.value === selection)) {
|
||||
selection = values.find(v => v.selected).value
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
value.localSelected = value.value === selection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const stripTempVar = (tempVarName: string): string =>
|
||||
tempVarName.substr(1, tempVarName.length - 2)
|
||||
|
||||
export const generateURLQueryParamsFromTempVars = (
|
||||
tempVars: Template[]
|
||||
): URLQueryParams => {
|
||||
const urlQueryParams = {}
|
||||
|
||||
tempVars.forEach(({tempVar, values}) => {
|
||||
const localSelected = values.find(value => value.localSelected === true)
|
||||
const strippedTempVar = stripTempVar(tempVar)
|
||||
|
||||
urlQueryParams[strippedTempVar] = _.get(localSelected, 'value', '')
|
||||
})
|
||||
|
||||
return urlQueryParams
|
||||
}
|
||||
|
||||
const isValidTempVarOverride = (
|
||||
values: TemplateValue[],
|
||||
overrideValue: string
|
||||
): boolean => !!values.find(({value}) => value === overrideValue)
|
||||
|
||||
const reconcileTempVarsWithOverrides = (
|
||||
currentTempVars: Template[],
|
||||
tempVarOverrides: URLQueryParams
|
||||
): Template[] => {
|
||||
if (!tempVarOverrides) {
|
||||
return currentTempVars
|
||||
}
|
||||
const reconciledTempVars = currentTempVars.map(tempVar => {
|
||||
const {tempVar: name, values} = tempVar
|
||||
const strippedTempVar = stripTempVar(name)
|
||||
const overrideValue = tempVarOverrides[strippedTempVar]
|
||||
if (overrideValue && isValidTempVarOverride(values, overrideValue)) {
|
||||
const overriddenValues = values.map(tempVarValue => {
|
||||
const {value} = tempVarValue
|
||||
if (value === overrideValue) {
|
||||
return {...tempVarValue, localSelected: true}
|
||||
}
|
||||
return {...tempVarValue, localSelected: false}
|
||||
})
|
||||
return {...tempVar, values: overriddenValues}
|
||||
} else {
|
||||
const valuesWithLocalSelected = values.map(tempVarValue => {
|
||||
const isSelected = tempVarValue.selected
|
||||
return {...tempVarValue, localSelected: isSelected}
|
||||
})
|
||||
return {...tempVar, values: valuesWithLocalSelected}
|
||||
}
|
||||
})
|
||||
|
||||
return reconciledTempVars
|
||||
}
|
||||
|
||||
export const applyDashboardTempVarOverrides = (
|
||||
dashboard: Dashboard,
|
||||
tempVarOverrides: URLQueryParams
|
||||
): Dashboard => ({
|
||||
...dashboard,
|
||||
templates: reconcileTempVarsWithOverrides(
|
||||
dashboard.templates,
|
||||
tempVarOverrides
|
||||
),
|
||||
})
|
||||
|
||||
export const findUpdatedTempVarsInURLQueryParams = (
|
||||
tempVars: Template[],
|
||||
urlQueryParams: URLQueryParams
|
||||
): TemplateUpdate[] => {
|
||||
const urlQueryParamsTempVarsWithInvalidValues = _.reduce(
|
||||
urlQueryParams,
|
||||
(acc, v, k) => {
|
||||
const matchedTempVar = tempVars.find(
|
||||
({tempVar}) => stripTempVar(tempVar) === k
|
||||
)
|
||||
if (matchedTempVar) {
|
||||
const isDifferentTempVarValue = !!matchedTempVar.values.find(
|
||||
({value, selected}) => selected && value !== v
|
||||
)
|
||||
if (isDifferentTempVarValue) {
|
||||
acc.push({key: k, value: v})
|
||||
}
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return urlQueryParamsTempVarsWithInvalidValues
|
||||
}
|
||||
|
||||
export const findInvalidTempVarsInURLQuery = (
|
||||
tempVars: Template[],
|
||||
urlQueryParams: URLQueryParams
|
||||
): TemplateUpdate[] => {
|
||||
const urlQueryParamsTempVarsWithInvalidValues = _.reduce(
|
||||
urlQueryParams,
|
||||
(acc, v, k) => {
|
||||
const matchedTempVar = tempVars.find(
|
||||
({tempVar}) => stripTempVar(tempVar) === k
|
||||
)
|
||||
if (matchedTempVar) {
|
||||
const isValidTempVarValue = !!matchedTempVar.values.find(
|
||||
({value}) => value === v
|
||||
)
|
||||
if (!isValidTempVarValue) {
|
||||
acc.push({key: k, value: v})
|
||||
}
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return urlQueryParamsTempVarsWithInvalidValues
|
||||
}
|
||||
|
||||
const makeSelected = (template: Template, value: string): Template => {
|
||||
const found = template.values.find(v => v.value === value)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import {connect} from 'react-redux'
|
|||
import {bindActionCreators} from 'redux'
|
||||
import {withRouter, InjectedRouter} from 'react-router'
|
||||
import {Location} from 'history'
|
||||
import queryString from 'query-string'
|
||||
import qs from 'qs'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
|
@ -64,7 +64,7 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
|
||||
public componentDidMount() {
|
||||
const {source} = this.props
|
||||
const {query} = queryString.parse(location.search)
|
||||
const {query} = qs.parse(location.search, {ignoreQueryPrefix: true})
|
||||
if (query && query.length) {
|
||||
const qc = this.props.queryConfigs[0]
|
||||
this.props.queryConfigActions.editRawTextAsync(
|
||||
|
@ -80,10 +80,10 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
const {queryConfigs, timeRange} = nextProps
|
||||
|
||||
const query = buildRawText(_.get(queryConfigs, ['0'], ''), timeRange)
|
||||
const qsCurrent = queryString.parse(location.search)
|
||||
const qsCurrent = qs.parse(location.search, {ignoreQueryPrefix: true})
|
||||
|
||||
if (query.length && qsCurrent.query !== query) {
|
||||
const qsNew = queryString.stringify({query})
|
||||
const qsNew = qs.stringify({query})
|
||||
const pathname = stripPrefix(location.pathname)
|
||||
router.push(`${pathname}?${qsNew}`)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
// Middleware generally used for actions needing parsed queryStrings
|
||||
import queryString from 'query-string'
|
||||
import qs from 'qs'
|
||||
|
||||
import {enablePresentationMode} from 'src/shared/actions/app'
|
||||
|
||||
export const queryStringConfig = () => dispatch => action => {
|
||||
dispatch(action)
|
||||
const urlQueryParams = queryString.parse(window.location.search)
|
||||
|
||||
const urlQueryParams = qs.parse(window.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
|
||||
if (urlQueryParams.present === 'true') {
|
||||
dispatch(enablePresentationMode())
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars'
|
||||
import {parseMetaQuery} from 'src/tempVars/parsing'
|
||||
import templateReplace from 'src/tempVars/utils/replace'
|
||||
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/tempVars/constants'
|
||||
|
||||
import {Template} from 'src/types'
|
||||
|
||||
export const hydrateTemplate = async (
|
||||
proxyLink: string,
|
||||
template: Template,
|
||||
templates: Template[]
|
||||
): Promise<Template> => {
|
||||
if (!template.query || !template.query.influxql) {
|
||||
return template
|
||||
}
|
||||
|
||||
const query = templateReplace(makeQueryForTemplate(template.query), templates)
|
||||
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 => {
|
||||
return {
|
||||
type,
|
||||
value,
|
||||
selected: value === selectedValue,
|
||||
localSelected: value === selectedLocalValue,
|
||||
}
|
||||
})
|
||||
|
||||
if (templateValues.length && !templateValues.find(v => v.selected)) {
|
||||
// Handle stale selected value
|
||||
templateValues[0].selected = true
|
||||
}
|
||||
|
||||
return {...template, values: templateValues}
|
||||
}
|
||||
|
||||
export const isTemplateNested = (template: Template): boolean => {
|
||||
// A _nested template_ is one whose query references other templates
|
||||
return (
|
||||
template.query &&
|
||||
template.query.influxql &&
|
||||
!!makeQueryForTemplate(template.query).match(/(.*:.+:.*)+/)
|
||||
)
|
||||
}
|
||||
|
||||
const getSelectedValue = (template: Template): string | false => {
|
||||
const selected = template.values.find(v => v.selected)
|
||||
|
||||
return selected ? selected.value : false
|
||||
}
|
||||
|
||||
const getLocalSelectedValue = (template: Template): string | false => {
|
||||
const selected = template.values.find(v => v.localSelected)
|
||||
|
||||
return selected ? selected.value : false
|
||||
}
|
|
@ -8,9 +8,9 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
import TemplatePreviewList from 'src/tempVars/components/TemplatePreviewList'
|
||||
import DragAndDrop from 'src/shared/components/DragAndDrop'
|
||||
import {notifyCSVUploadFailed} from 'src/shared/copy/notifications'
|
||||
import {trimAndRemoveQuotes} from 'src/tempVars/utils'
|
||||
|
||||
import {TemplateBuilderProps, TemplateValueType, TemplateValue} from 'src/types'
|
||||
import {trimAndRemoveQuotes} from 'src/tempVars/utils/parsing'
|
||||
|
||||
interface State {
|
||||
templateValuesString: string
|
||||
|
|
|
@ -23,6 +23,7 @@ class FieldKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
|||
public render() {
|
||||
const {
|
||||
template,
|
||||
templates,
|
||||
source,
|
||||
onUpdateTemplate,
|
||||
onUpdateDefaultTemplateValue,
|
||||
|
@ -34,6 +35,7 @@ class FieldKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
|||
templateValueType={TemplateValueType.FieldKey}
|
||||
fetchKeys={fetchKeys}
|
||||
template={template}
|
||||
templates={templates}
|
||||
source={source}
|
||||
onUpdateTemplate={onUpdateTemplate}
|
||||
onUpdateDefaultTemplateValue={onUpdateDefaultTemplateValue}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from 'src/shared/copy/notifications'
|
||||
|
||||
import {TemplateBuilderProps, TemplateValueType} from 'src/types'
|
||||
import {trimAndRemoveQuotes} from 'src/tempVars/utils/parsing'
|
||||
import {trimAndRemoveQuotes} from 'src/tempVars/utils'
|
||||
|
||||
interface State {
|
||||
templateValuesString: string
|
||||
|
|
|
@ -2,16 +2,12 @@ import React, {PureComponent, ChangeEvent} from 'react'
|
|||
import _ from 'lodash'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
|
||||
import {parseMetaQuery, isInvalidMetaQuery} from 'src/tempVars/utils/parsing'
|
||||
import {hydrateTemplate} from 'src/tempVars/apis'
|
||||
import {isInvalidMetaQuery} from 'src/tempVars/parsing'
|
||||
|
||||
import {
|
||||
TemplateBuilderProps,
|
||||
RemoteDataState,
|
||||
TemplateValueType,
|
||||
} from 'src/types'
|
||||
import {TemplateBuilderProps, RemoteDataState} from 'src/types'
|
||||
|
||||
const DEBOUNCE_DELAY = 750
|
||||
|
||||
|
@ -115,7 +111,7 @@ class CustomMetaQueryTemplateBuilder extends PureComponent<
|
|||
}
|
||||
|
||||
private executeQuery = async (): Promise<void> => {
|
||||
const {template, source, onUpdateTemplate} = this.props
|
||||
const {template, templates, source, onUpdateTemplate} = this.props
|
||||
const {metaQuery} = this.state
|
||||
|
||||
if (this.isInvalidMetaQuery) {
|
||||
|
@ -125,34 +121,21 @@ class CustomMetaQueryTemplateBuilder extends PureComponent<
|
|||
this.setState({metaQueryResultsStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await proxy({
|
||||
source: source.links.proxy,
|
||||
query: metaQuery,
|
||||
})
|
||||
const templateWithQuery = {
|
||||
...template,
|
||||
query: {influxql: metaQuery},
|
||||
}
|
||||
|
||||
const metaQueryResults = parseMetaQuery(metaQuery, data)
|
||||
const nextTemplate = await hydrateTemplate(
|
||||
source.links.proxy,
|
||||
templateWithQuery,
|
||||
templates
|
||||
)
|
||||
|
||||
this.setState({metaQueryResultsStatus: RemoteDataState.Done})
|
||||
|
||||
const nextValues = metaQueryResults.map(result => {
|
||||
return {
|
||||
type: TemplateValueType.MetaQuery,
|
||||
value: result,
|
||||
selected: false,
|
||||
localSelected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues[0]) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
const nextTemplate = {
|
||||
...template,
|
||||
values: nextValues,
|
||||
query: {
|
||||
influxql: metaQuery,
|
||||
},
|
||||
if (nextTemplate.values[0]) {
|
||||
nextTemplate.values[0].selected = true
|
||||
}
|
||||
|
||||
onUpdateTemplate(nextTemplate)
|
||||
|
|
|
@ -26,6 +26,7 @@ class TagKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
|||
public render() {
|
||||
const {
|
||||
template,
|
||||
templates,
|
||||
source,
|
||||
onUpdateTemplate,
|
||||
onUpdateDefaultTemplateValue,
|
||||
|
@ -37,6 +38,7 @@ class TagKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
|||
templateValueType={TemplateValueType.TagKey}
|
||||
fetchKeys={fetchTagKeys}
|
||||
template={template}
|
||||
templates={templates}
|
||||
source={source}
|
||||
onUpdateTemplate={onUpdateTemplate}
|
||||
onUpdateDefaultTemplateValue={onUpdateDefaultTemplateValue}
|
||||
|
|
|
@ -51,6 +51,7 @@ class TemplateControlBar extends Component<Props, State> {
|
|||
meRole={meRole}
|
||||
isUsingAuth={isUsingAuth}
|
||||
template={template}
|
||||
templates={templates}
|
||||
source={source}
|
||||
onPickTemplate={onPickTemplate}
|
||||
onCreateTemplate={this.handleCreateTemplate}
|
||||
|
@ -66,6 +67,7 @@ class TemplateControlBar extends Component<Props, State> {
|
|||
)}
|
||||
<OverlayTechnology visible={isAdding}>
|
||||
<TemplateVariableEditor
|
||||
templates={templates}
|
||||
source={source}
|
||||
onCreate={this.handleCreateTemplate}
|
||||
onCancel={this.handleCancelAddVariable}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {Template, Source, TemplateValueType} from 'src/types'
|
|||
|
||||
interface Props {
|
||||
template: Template
|
||||
templates: Template[]
|
||||
meRole: string
|
||||
isUsingAuth: boolean
|
||||
source: Source
|
||||
|
@ -33,7 +34,13 @@ class TemplateControlDropdown extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {template, source, onPickTemplate, onCreateTemplate} = this.props
|
||||
const {
|
||||
template,
|
||||
templates,
|
||||
source,
|
||||
onPickTemplate,
|
||||
onCreateTemplate,
|
||||
} = this.props
|
||||
const {isEditing} = this.state
|
||||
|
||||
const dropdownItems = template.values.map(value => {
|
||||
|
@ -73,6 +80,7 @@ class TemplateControlDropdown extends PureComponent<Props, State> {
|
|||
<OverlayTechnology visible={isEditing}>
|
||||
<TemplateVariableEditor
|
||||
template={template}
|
||||
templates={templates}
|
||||
source={source}
|
||||
onCreate={onCreateTemplate}
|
||||
onUpdate={this.handleUpdateTemplate}
|
||||
|
|
|
@ -16,6 +16,7 @@ import ConfirmButton from 'src/shared/components/ConfirmButton'
|
|||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {notify as notifyActionCreator} from 'src/shared/actions/notifications'
|
||||
|
||||
import {formatTempVar} from 'src/tempVars/utils'
|
||||
import {
|
||||
reconcileSelectedAndLocalSelectedValues,
|
||||
pickSelected,
|
||||
|
@ -49,6 +50,7 @@ import {FIVE_SECONDS} from 'src/shared/constants/index'
|
|||
interface Props {
|
||||
// We will assume we are creating a new template if none is passed in
|
||||
template?: Template
|
||||
templates: Template[]
|
||||
source: Source
|
||||
onCancel: () => void
|
||||
onCreate?: (template: Template) => Promise<any>
|
||||
|
@ -75,8 +77,6 @@ const TEMPLATE_BUILDERS = {
|
|||
[TemplateType.MetaQuery]: MetaQueryTemplateBuilder,
|
||||
}
|
||||
|
||||
const formatName = name => `:${name.replace(/:/g, '').replace(/\s/g, '')}:`
|
||||
|
||||
const DEFAULT_TEMPLATE = DEFAULT_TEMPLATES[TemplateType.Databases]
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -107,7 +107,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {source, onCancel, notify} = this.props
|
||||
const {source, onCancel, notify, templates} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
const TemplateBuilder = this.templateBuilder
|
||||
|
||||
|
@ -158,6 +158,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
</div>
|
||||
<TemplateBuilder
|
||||
template={nextTemplate}
|
||||
templates={templates}
|
||||
source={source}
|
||||
onUpdateTemplate={this.handleUpdateTemplate}
|
||||
notify={notify}
|
||||
|
@ -274,7 +275,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
private formatName = (): void => {
|
||||
const {nextTemplate} = this.state
|
||||
|
||||
let tempVar = formatName(nextTemplate.tempVar)
|
||||
let tempVar = formatTempVar(nextTemplate.tempVar)
|
||||
|
||||
if (tempVar === '::') {
|
||||
tempVar = ''
|
||||
|
@ -290,7 +291,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
const {onUpdate, onCreate, notify} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
|
||||
nextTemplate.tempVar = formatName(nextTemplate.tempVar)
|
||||
nextTemplate.tempVar = formatTempVar(nextTemplate.tempVar)
|
||||
|
||||
this.setState({savingStatus: RemoteDataState.Loading})
|
||||
|
||||
|
@ -335,7 +336,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
return (
|
||||
tempVar !== '' &&
|
||||
canSaveValues &&
|
||||
!RESERVED_TEMPLATE_NAMES.includes(formatName(tempVar)) &&
|
||||
!RESERVED_TEMPLATE_NAMES.includes(formatTempVar(tempVar)) &&
|
||||
!this.isSaving
|
||||
)
|
||||
}
|
||||
|
|
|
@ -82,9 +82,3 @@ const EXTRACTORS = {
|
|||
},
|
||||
'SHOW SERIES': parsed => parsed.series,
|
||||
}
|
||||
|
||||
export const trimAndRemoveQuotes = elt => {
|
||||
const trimmed = elt.trim()
|
||||
const dequoted = trimmed.replace(/(^")|("$)/g, '')
|
||||
return dequoted
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export const trimAndRemoveQuotes = elt => {
|
||||
const trimmed = elt.trim()
|
||||
const dequoted = trimmed.replace(/(^")|("$)/g, '')
|
||||
|
||||
return dequoted
|
||||
}
|
||||
|
||||
export const formatTempVar = name =>
|
||||
`:${name.replace(/:/g, '').replace(/\s/g, '')}:`
|
|
@ -1,8 +1,5 @@
|
|||
import {Dispatch} from 'redux'
|
||||
import {InjectedRouter} from 'react-router'
|
||||
import {LocationAction} from 'react-router-redux'
|
||||
import {Source} from 'src/types'
|
||||
import {Location} from 'history'
|
||||
import * as DashboardsModels from 'src/types/dashboards'
|
||||
import * as DashboardsReducers from 'src/types/reducers/dashboards'
|
||||
import * as ErrorsActions from 'src/types/actions/errors'
|
||||
|
@ -69,18 +66,6 @@ export interface SetTimeRangeAction {
|
|||
}
|
||||
}
|
||||
|
||||
export type SetZoomedTimeRangeDispatcher = (
|
||||
zoomedTimeRange: QueriesModels.TimeRange,
|
||||
location: Location
|
||||
) => SetZoomedTimeRangeThunk
|
||||
|
||||
export type SetZoomedTimeRangeThunk = (
|
||||
dispatch: Dispatch<
|
||||
| SetZoomedTimeRangeActionCreator
|
||||
| SyncURLQueryFromQueryParamsObjectDispatcher
|
||||
>
|
||||
) => Promise<void>
|
||||
|
||||
export type SetZoomedTimeRangeActionCreator = (
|
||||
zoomedTimeRange: QueriesModels.TimeRange
|
||||
) => SetZoomedTimeRangeAction
|
||||
|
@ -242,31 +227,10 @@ export interface TemplateVariableLocalSelectedAction {
|
|||
}
|
||||
}
|
||||
|
||||
export type TemplateVariablesLocalSelectedByNameActionCreator = (
|
||||
dashboardID: number,
|
||||
queryParams: TempVarsModels.URLQueryParams
|
||||
) => TemplateVariablesLocalSelectedByNameAction
|
||||
|
||||
export interface TemplateVariablesLocalSelectedByNameAction {
|
||||
type: 'TEMPLATE_VARIABLES_SELECTED_BY_NAME'
|
||||
export interface UpdateTemplatesAction {
|
||||
type: 'UPDATE_TEMPLATES'
|
||||
payload: {
|
||||
dashboardID: number
|
||||
queryParams: TempVarsModels.URLQueryParams
|
||||
}
|
||||
}
|
||||
|
||||
export type EditTemplateVariableValuesActionCreator = (
|
||||
dashboardID: number,
|
||||
templateID: string,
|
||||
values: any[]
|
||||
) => EditTemplateVariableValuesAction
|
||||
|
||||
export interface EditTemplateVariableValuesAction {
|
||||
type: 'EDIT_TEMPLATE_VARIABLE_VALUES'
|
||||
payload: {
|
||||
dashboardID: number
|
||||
templateID: string
|
||||
values: any[]
|
||||
templates: TempVarsModels.Template[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,50 +305,7 @@ export type UpdateDashboardCellThunk = (
|
|||
>
|
||||
) => Promise<void>
|
||||
|
||||
export type SyncURLQueryFromQueryParamsObjectDispatcher = (
|
||||
location: Location,
|
||||
updatedURLQueryParams: TempVarsModels.URLQueryParams,
|
||||
deletedURLQueryParams?: TempVarsModels.URLQueryParams
|
||||
) => SyncURLQueryFromQueryParamsObjectActionCreator
|
||||
|
||||
export type SyncURLQueryFromTempVarsDispatcher = (
|
||||
location: Location,
|
||||
tempVars: TempVarsModels.Template[],
|
||||
deletedTempVars: TempVarsModels.Template[],
|
||||
urlQueryParamsTimeRanges?: TempVarsModels.URLQueryParams
|
||||
) => SyncURLQueryFromQueryParamsObjectActionCreator
|
||||
|
||||
export type SyncURLQueryFromQueryParamsObjectActionCreator = (
|
||||
dispatch: Dispatch<LocationAction>
|
||||
) => void
|
||||
|
||||
export type SyncDashboardTempVarsFromURLQueryParamsDispatcher = (
|
||||
dispatch: Dispatch<
|
||||
| NotificationsActions.PublishNotificationActionCreator
|
||||
| TemplateVariableLocalSelectedAction
|
||||
>,
|
||||
getState: () => DashboardsReducers.Dashboards & DashboardsReducers.Auth
|
||||
) => void
|
||||
|
||||
export type SyncDashboardTimeRangeFromURLQueryParamsDispatcher = (
|
||||
dispatch: Dispatch<NotificationsActions.PublishNotificationActionCreator>,
|
||||
getState: () => DashboardsReducers.Dashboards & DashboardsReducers.DashTimeV1
|
||||
) => void
|
||||
|
||||
export type SyncDashboardFromURLQueryParamsDispatcher = (
|
||||
dispatch: Dispatch<
|
||||
| SyncDashboardTempVarsFromURLQueryParamsDispatcher
|
||||
| SyncDashboardTimeRangeFromURLQueryParamsDispatcher
|
||||
>
|
||||
) => void
|
||||
|
||||
export type GetDashboardWithHydratedAndSyncedTempVarsAsyncDispatcher = (
|
||||
dashboardID: number,
|
||||
source: Source,
|
||||
router: InjectedRouter,
|
||||
location: Location
|
||||
) => GetDashboardWithHydratedAndSyncedTempVarsAsyncThunk
|
||||
|
||||
export type GetDashboardWithHydratedAndSyncedTempVarsAsyncThunk = (
|
||||
dispatch: Dispatch<NotificationsActions.PublishNotificationActionCreator>
|
||||
) => Promise<void>
|
||||
export type GetDashboardWithTemplates = (
|
||||
dashboardId: number,
|
||||
source: Source
|
||||
) => ((dispatch: Dispatch<any>) => Promise<void>)
|
||||
|
|
|
@ -134,3 +134,8 @@ export interface DashboardSwitcherLink {
|
|||
text: string
|
||||
to: string
|
||||
}
|
||||
|
||||
export interface TemplateQPSelections {
|
||||
// e.g. {':my-db:': 'telegraf'}
|
||||
[tempVar: string]: string
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
Template,
|
||||
TemplateQuery,
|
||||
TemplateValue,
|
||||
URLQueryParams,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
TemplateUpdate,
|
||||
|
@ -103,7 +102,6 @@ export {
|
|||
ScriptStatus,
|
||||
SchemaFilter,
|
||||
RemoteDataState,
|
||||
URLQueryParams,
|
||||
JSONFeedData,
|
||||
AnnotationInterface,
|
||||
TemplateType,
|
||||
|
|
|
@ -59,12 +59,16 @@ export interface TemplateUpdate {
|
|||
value: string
|
||||
}
|
||||
|
||||
export interface URLQueryParams {
|
||||
[key: string]: string
|
||||
export interface TimeRangeQueryParams {
|
||||
lower?: string
|
||||
upper?: string
|
||||
zoomedLower?: string
|
||||
zoomedUpper?: string
|
||||
}
|
||||
|
||||
export interface TemplateBuilderProps {
|
||||
template: Template
|
||||
templates: Template[]
|
||||
source: Source
|
||||
onUpdateTemplate: (nextTemplate: Template) => void
|
||||
onUpdateDefaultTemplateValue: (item: TemplateValue) => void
|
||||
|
|
|
@ -12,9 +12,8 @@ import {
|
|||
syncDashboardCell,
|
||||
deleteDashboardFailed,
|
||||
templateVariableLocalSelected,
|
||||
editTemplateVariableValues,
|
||||
templateVariablesLocalSelectedByName,
|
||||
setActiveCell,
|
||||
updateTemplates,
|
||||
} from 'src/dashboards/actions'
|
||||
|
||||
let state
|
||||
|
@ -129,34 +128,6 @@ describe('DataExplorer.Reducers.UI', () => {
|
|||
expect(actual.dashboards[0].templates[0].values[2].localSelected).toBe(true)
|
||||
})
|
||||
|
||||
it('can select template variable values by name', () => {
|
||||
const dash = _.cloneDeep(d1)
|
||||
state = {
|
||||
dashboards: [dash],
|
||||
}
|
||||
|
||||
const localSelected = {region: 'us-west', temperature: '99.1'}
|
||||
const actual = reducer(
|
||||
state,
|
||||
templateVariablesLocalSelectedByName(dash.id, localSelected)
|
||||
)
|
||||
|
||||
expect(actual.dashboards[0].templates[0].values[0].localSelected).toBe(true)
|
||||
expect(actual.dashboards[0].templates[0].values[1].localSelected).toBe(
|
||||
false
|
||||
)
|
||||
expect(actual.dashboards[0].templates[0].values[2].localSelected).toBe(
|
||||
false
|
||||
)
|
||||
expect(actual.dashboards[0].templates[1].values[0].localSelected).toBe(
|
||||
false
|
||||
)
|
||||
expect(actual.dashboards[0].templates[1].values[1].localSelected).toBe(true)
|
||||
expect(actual.dashboards[0].templates[1].values[2].localSelected).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
describe('SET_ACTIVE_CELL', () => {
|
||||
it('can set the active cell', () => {
|
||||
const activeCellID = '1'
|
||||
|
@ -166,56 +137,81 @@ describe('DataExplorer.Reducers.UI', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('EDIT_TEMPLATE_VARIABLE_VALUES', () => {
|
||||
it('can edit the tempvar values', () => {
|
||||
const actual = reducer(
|
||||
{...initialState, dashboards},
|
||||
editTemplateVariableValues(d1.id, template.id, ['v1', 'v2'])
|
||||
)
|
||||
describe('UPDATE_TEMPLATE_VARIABLES', () => {
|
||||
it('can update template variables', () => {
|
||||
const thisState = {
|
||||
...initialState,
|
||||
dashboards: [
|
||||
{
|
||||
...dashboard,
|
||||
templates: [
|
||||
{
|
||||
id: '0',
|
||||
tempVar: ':foo:',
|
||||
label: '',
|
||||
type: TemplateType.CSV,
|
||||
values: [],
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
tempVar: ':bar:',
|
||||
label: '',
|
||||
type: TemplateType.CSV,
|
||||
values: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
tempVar: ':baz:',
|
||||
label: '',
|
||||
type: TemplateType.CSV,
|
||||
values: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const expected = [
|
||||
const newTemplates = [
|
||||
{
|
||||
localSelected: false,
|
||||
selected: false,
|
||||
value: 'v1',
|
||||
type: 'tagKey',
|
||||
id: '0',
|
||||
tempVar: ':foo:',
|
||||
label: '',
|
||||
type: TemplateType.CSV,
|
||||
values: [
|
||||
{
|
||||
type: TemplateValueType.CSV,
|
||||
value: '',
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
localSelected: false,
|
||||
selected: false,
|
||||
value: 'v2',
|
||||
type: 'tagKey',
|
||||
id: '1',
|
||||
tempVar: ':bar:',
|
||||
label: '',
|
||||
type: TemplateType.CSV,
|
||||
values: [
|
||||
{
|
||||
type: TemplateValueType.CSV,
|
||||
value: '',
|
||||
selected: false,
|
||||
localSelected: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
expect(actual.dashboards[0].templates[0].values).toEqual(expected)
|
||||
})
|
||||
const result = reducer(thisState, updateTemplates(newTemplates))
|
||||
|
||||
it('can handle an empty template.values', () => {
|
||||
const ts = [{...template, values: []}]
|
||||
const ds = [{...d1, templates: ts}]
|
||||
// Variables present in payload are updated
|
||||
expect(result.dashboards[0].templates).toContainEqual(newTemplates[0])
|
||||
expect(result.dashboards[0].templates).toContainEqual(newTemplates[1])
|
||||
|
||||
const actual = reducer(
|
||||
{...initialState, dashboards: ds},
|
||||
editTemplateVariableValues(d1.id, template.id, ['v1', 'v2'])
|
||||
// Variables not present in action payload are left untouched
|
||||
expect(result.dashboards[0].templates).toContainEqual(
|
||||
thisState.dashboards[0].templates[2]
|
||||
)
|
||||
|
||||
const expected = [
|
||||
{
|
||||
localSelected: false,
|
||||
selected: false,
|
||||
value: 'v1',
|
||||
type: 'tagKey',
|
||||
},
|
||||
{
|
||||
localSelected: false,
|
||||
selected: false,
|
||||
value: 'v2',
|
||||
type: 'tagKey',
|
||||
},
|
||||
]
|
||||
|
||||
expect(actual.dashboards[0].templates[0].values).toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -8,21 +8,24 @@ import {source} from 'test/resources'
|
|||
|
||||
import {TemplateType, TemplateValueType} from 'src/types'
|
||||
|
||||
const template = {
|
||||
id: '0',
|
||||
tempVar: ':my-var:',
|
||||
label: '',
|
||||
type: TemplateType.Databases,
|
||||
values: [
|
||||
{
|
||||
value: 'db0',
|
||||
type: TemplateValueType.Database,
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
template: {
|
||||
id: '0',
|
||||
tempVar: ':my-var:',
|
||||
label: '',
|
||||
type: TemplateType.Databases,
|
||||
values: [
|
||||
{
|
||||
value: 'db0',
|
||||
type: TemplateValueType.Database,
|
||||
selected: true,
|
||||
localSelected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
template,
|
||||
templates: [template],
|
||||
meRole: 'EDITOR',
|
||||
isUsingAuth: true,
|
||||
source,
|
||||
|
|
16
ui/yarn.lock
16
ui/yarn.lock
|
@ -87,6 +87,10 @@
|
|||
version "15.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1"
|
||||
|
||||
"@types/qs@^6.5.1":
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7"
|
||||
|
||||
"@types/react-dnd-html5-backend@^2.1.9":
|
||||
version "2.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dnd-html5-backend/-/react-dnd-html5-backend-2.1.9.tgz#dfc9efe2a68bd12407815a2d61ddfd18bb8686fb"
|
||||
|
@ -7588,6 +7592,10 @@ qs@6.5.1, qs@~6.5.1:
|
|||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||
|
||||
qs@^6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
||||
qs@~6.3.0:
|
||||
version "6.3.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
|
||||
|
@ -7599,14 +7607,6 @@ query-string@^4.1.0, query-string@^4.2.2:
|
|||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
|
|
Loading…
Reference in New Issue