feat(ui): Template variables can now select their source database
parent
16ac349228
commit
fff7836818
kv/internal
server
ui
src
dashboards
flux/components
tempVars
components
utils
types
test/tempVars/utils
|
@ -8,6 +8,7 @@
|
|||
|
||||
1. [#5348](https://github.com/influxdata/chronograf/pull/5348): Add query parameter for dashboard auto refresh
|
||||
1. [#5352](https://github.com/influxdata/chronograf/pull/5352): Add etcd as an alternate backend store
|
||||
1. [#5367](https://github.com/influxdata/chronograf/pull/5367): Template variables can now select their source database
|
||||
|
||||
### Other
|
||||
|
||||
|
|
|
@ -183,10 +183,11 @@ type TemplateID string
|
|||
// Template represents a series of choices to replace TemplateVars within InfluxQL
|
||||
type Template struct {
|
||||
TemplateVar
|
||||
ID TemplateID `json:"id"` // ID is the unique ID associated with this template
|
||||
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, csv, constant, measurements, databases, map, influxql, text
|
||||
Label string `json:"label"` // Label is a user-facing description of the Template
|
||||
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
|
||||
ID TemplateID `json:"id"` // ID is the unique ID associated with this template
|
||||
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, csv, constant, measurements, databases, map, influxql, text
|
||||
Label string `json:"label"` // Label is a user-facing description of the Template
|
||||
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
|
||||
SourceID int `json:"sourceID,string"` // Source is the id of the data source
|
||||
}
|
||||
|
||||
// Query retrieves a Response from a TimeSeries.
|
||||
|
|
|
@ -354,11 +354,12 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
|
|||
}
|
||||
|
||||
template := &Template{
|
||||
ID: string(t.ID),
|
||||
TempVar: t.Var,
|
||||
Values: vals,
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
ID: string(t.ID),
|
||||
TempVar: t.Var,
|
||||
Values: vals,
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
SourceID: int64(t.SourceID),
|
||||
}
|
||||
if t.Query != nil {
|
||||
template.Query = &TemplateQuery{
|
||||
|
@ -544,8 +545,9 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
|
|||
Var: t.TempVar,
|
||||
Values: vals,
|
||||
},
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
SourceID: int(t.SourceID),
|
||||
}
|
||||
|
||||
if t.Query != nil {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -97,6 +97,7 @@ message Template {
|
|||
string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
|
||||
string label = 5; // Label is a user-facing description of the Template
|
||||
TemplateQuery query = 6; // Query is used to generate the choices for a template
|
||||
int64 sourceID = 7; // Source is the id of the data source
|
||||
}
|
||||
|
||||
message TemplateValue {
|
||||
|
@ -233,7 +234,7 @@ message LogViewerColumn {
|
|||
repeated ColumnEncoding Encodings = 3; // Encodings is the array of encoded properties associated with a log viewer column
|
||||
}
|
||||
|
||||
message ColumnEncoding {
|
||||
message ColumnEncoding {
|
||||
string Type = 1; // Type is the purpose of the encoding, for example: severity color
|
||||
string Value = 2; // Value is what the encoding corresponds to
|
||||
string Name = 3; // Name is the optional encoding name
|
||||
|
|
|
@ -43,7 +43,7 @@ func Test_Protoboards(t *testing.T) {
|
|||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json",
|
||||
body: `{"protoboards":[{"id":"1","meta":{"name":"protodashboard 1","icon":"http://example.com/icon.png","version":"1.2.3","measurements":["m1","m2"],"dashboardVersion":"1.7.0","description":"this is great","author":"Chronogiraffe","license":"Apache-2.0","url":"http://example.com"},"data":{"cells":[{"x":0,"y":0,"w":0,"h":0,"name":"","queries":null,"axes":null,"type":"","colors":null,"legend":{},"tableOptions":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":null,"timeFormat":"","decimalPlaces":{"isEnforced":false,"digits":0},"note":"","noteVisibility":""}],"templates":[{"tempVar":"","values":null,"id":"","type":"","label":""}]},"links":{"self":"/chronograf/v1/protoboards/1"}},{"id":"2","meta":{"name":"protodashboard 2","icon":"http://example.com/icon.png","version":"1.2.3","measurements":["m1","m2"],"dashboardVersion":"1.7.0","description":"this is great","author":"Chronogiraffe","license":"Apache-2.0","url":"http://example.com"},"data":{"cells":[{"x":8,"y":0,"w":3,"h":5,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_steal\") AS \"mean_usage_steal\", mean(\"usage_system\") AS \"mean_usage_system\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"host\"='denizs-MacBook-Pro.local' GROUP BY time(:interval:) FILL(null)","queryConfig":{"database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_steal","args":[{"value":"usage_steal","type":"field","alias":""}]},{"value":"mean","type":"func","alias":"mean_usage_system","args":[{"value":"usage_steal","type":"field","alias":""}]}],"tags":{"host":["denizs-MacBook-Pro.local"]},"groupBy":{"time":"auto","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":null,"range":null,"shifts":null},"source":"","type":"influxql"}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"}},"type":"line","colors":[],"legend":{},"tableOptions":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":[],"timeFormat":"","decimalPlaces":{"isEnforced":true,"digits":2},"note":"","noteVisibility":""}],"templates":null},"links":{"self":"/chronograf/v1/protoboards/2"}}]}`},
|
||||
body: `{"protoboards":[{"id":"1","meta":{"name":"protodashboard 1","icon":"http://example.com/icon.png","version":"1.2.3","measurements":["m1","m2"],"dashboardVersion":"1.7.0","description":"this is great","author":"Chronogiraffe","license":"Apache-2.0","url":"http://example.com"},"data":{"cells":[{"x":0,"y":0,"w":0,"h":0,"name":"","queries":null,"axes":null,"type":"","colors":null,"legend":{},"tableOptions":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":null,"timeFormat":"","decimalPlaces":{"isEnforced":false,"digits":0},"note":"","noteVisibility":""}],"templates":[{"tempVar":"","values":null,"id":"","type":"","label":"","sourceID":"0"}]},"links":{"self":"/chronograf/v1/protoboards/1"}},{"id":"2","meta":{"name":"protodashboard 2","icon":"http://example.com/icon.png","version":"1.2.3","measurements":["m1","m2"],"dashboardVersion":"1.7.0","description":"this is great","author":"Chronogiraffe","license":"Apache-2.0","url":"http://example.com"},"data":{"cells":[{"x":8,"y":0,"w":3,"h":5,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_steal\") AS \"mean_usage_steal\", mean(\"usage_system\") AS \"mean_usage_system\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"host\"='denizs-MacBook-Pro.local' GROUP BY time(:interval:) FILL(null)","queryConfig":{"database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_steal","args":[{"value":"usage_steal","type":"field","alias":""}]},{"value":"mean","type":"func","alias":"mean_usage_system","args":[{"value":"usage_steal","type":"field","alias":""}]}],"tags":{"host":["denizs-MacBook-Pro.local"]},"groupBy":{"time":"auto","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":null,"range":null,"shifts":null},"source":"","type":"influxql"}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"10","scale":"linear"}},"type":"line","colors":[],"legend":{},"tableOptions":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":[],"timeFormat":"","decimalPlaces":{"isEnforced":true,"digits":2},"note":"","noteVisibility":""}],"templates":null},"links":{"self":"/chronograf/v1/protoboards/2"}}]}`},
|
||||
arg: []chronograf.Protoboard{
|
||||
{
|
||||
ID: "1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "chronograf-ui",
|
||||
"version": "1.7.17",
|
||||
"version": "1.8.0",
|
||||
"private": false,
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
|
|
|
@ -731,7 +731,8 @@ const updateTimeRangeFromQueryParams = (dashboardID: string) => (
|
|||
|
||||
export const getDashboardWithTemplatesAsync = (
|
||||
dashboardId: string,
|
||||
source: Source
|
||||
source: Source,
|
||||
sources: Source[]
|
||||
) => async (dispatch): Promise<void> => {
|
||||
let dashboard: Dashboard
|
||||
|
||||
|
@ -747,7 +748,7 @@ export const getDashboardWithTemplatesAsync = (
|
|||
const selections = templateSelectionsFromQueryParams()
|
||||
|
||||
let templates
|
||||
templates = await hydrateTemplates(dashboard.templates, {
|
||||
templates = await hydrateTemplates(dashboard.templates, sources, {
|
||||
proxyUrl: source.links.proxy,
|
||||
selections,
|
||||
})
|
||||
|
@ -777,11 +778,12 @@ export const getDashboardWithTemplatesAsync = (
|
|||
|
||||
export const rehydrateTemplatesAsync = (
|
||||
dashboardId: string,
|
||||
source: Source
|
||||
source: Source,
|
||||
sources: Source[]
|
||||
) => async (dispatch, getState): Promise<void> => {
|
||||
const dashboard = getDashboard(getState(), dashboardId)
|
||||
|
||||
const templates = await hydrateTemplates(dashboard.templates, {
|
||||
const templates = await hydrateTemplates(dashboard.templates, sources, {
|
||||
proxyUrl: source.links.proxy,
|
||||
})
|
||||
|
||||
|
|
|
@ -182,8 +182,6 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
ceoTimeRange
|
||||
)
|
||||
return [...dashboardTemplates, dashboardTime, upperDashboardTime]
|
||||
|
||||
return dashboardTemplates
|
||||
}
|
||||
|
||||
private get isSaveable(): boolean {
|
||||
|
|
|
@ -50,6 +50,7 @@ const SourceSelector: SFC<Props> = ({
|
|||
isDynamicSourceSelected={isDynamicSourceSelected}
|
||||
onChangeSource={onChangeSource}
|
||||
onSelectDynamicSource={onSelectDynamicSource}
|
||||
widthPixels={250}
|
||||
/>
|
||||
<Radio>
|
||||
<Radio.Button
|
||||
|
|
|
@ -140,7 +140,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
public componentDidMount() {
|
||||
const {refreshRate, updateQueryParams} = this.props
|
||||
const compareOptionToRefreshRate = (r: AutoRefreshOption) =>
|
||||
r.milliseconds === refreshRate
|
||||
|
@ -176,7 +176,7 @@ class DashboardPage extends Component<Props, State> {
|
|||
|
||||
window.addEventListener('resize', this.handleWindowResize, true)
|
||||
|
||||
await this.getDashboard()
|
||||
this.getDashboard()
|
||||
|
||||
this.fetchAnnotations()
|
||||
this.getDashboardLinks()
|
||||
|
@ -383,10 +383,15 @@ class DashboardPage extends Component<Props, State> {
|
|||
this.setState({windowHeight: window.innerHeight})
|
||||
}
|
||||
|
||||
private getDashboard = async () => {
|
||||
const {dashboardID, source, getDashboardWithTemplatesAsync} = this.props
|
||||
private getDashboard = () => {
|
||||
const {
|
||||
dashboardID,
|
||||
source,
|
||||
sources,
|
||||
getDashboardWithTemplatesAsync,
|
||||
} = this.props
|
||||
|
||||
await getDashboardWithTemplatesAsync(dashboardID, source)
|
||||
getDashboardWithTemplatesAsync(dashboardID, source, sources)
|
||||
this.updateActiveDashboard()
|
||||
}
|
||||
|
||||
|
@ -516,12 +521,13 @@ class DashboardPage extends Component<Props, State> {
|
|||
const {
|
||||
dashboard,
|
||||
source,
|
||||
sources,
|
||||
templateVariableLocalSelected,
|
||||
rehydrateTemplatesAsync,
|
||||
} = this.props
|
||||
|
||||
templateVariableLocalSelected(dashboard.id, template.id, value)
|
||||
rehydrateTemplatesAsync(dashboard.id, source)
|
||||
rehydrateTemplatesAsync(dashboard.id, source, sources)
|
||||
}
|
||||
|
||||
private handleSaveTemplateVariables = async (
|
||||
|
|
|
@ -18,6 +18,7 @@ interface Props {
|
|||
isDynamicSourceSelected?: boolean
|
||||
onSelectDynamicSource?: () => void
|
||||
onChangeSource: (source: Source, type: QueryType) => void
|
||||
widthPixels?: number
|
||||
}
|
||||
|
||||
interface SourceDropdownItem {
|
||||
|
@ -31,7 +32,7 @@ class SourceDropdown extends PureComponent<Props> {
|
|||
<Dropdown
|
||||
onChange={this.handleSelect}
|
||||
selectedID={this.selectedID}
|
||||
widthPixels={250}
|
||||
widthPixels={this.props.widthPixels}
|
||||
>
|
||||
{this.dropdownItems}
|
||||
</Dropdown>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Libraries
|
||||
import React, {
|
||||
PureComponent,
|
||||
ComponentClass,
|
||||
|
@ -5,23 +6,26 @@ import React, {
|
|||
KeyboardEvent,
|
||||
} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
import {get, isEmpty} from 'lodash'
|
||||
|
||||
// Utils
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import OverlayContainer from 'src/reusable_ui/components/overlays/OverlayContainer'
|
||||
import OverlayHeading from 'src/reusable_ui/components/overlays/OverlayHeading'
|
||||
import OverlayBody from 'src/reusable_ui/components/overlays/OverlayBody'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
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,
|
||||
} from 'src/dashboards/utils/tempVars'
|
||||
|
||||
// Actions
|
||||
import {notify as notifyActionCreator} from 'src/shared/actions/notifications'
|
||||
|
||||
// Components
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
import OverlayContainer from 'src/reusable_ui/components/overlays/OverlayContainer'
|
||||
import OverlayHeading from 'src/reusable_ui/components/overlays/OverlayHeading'
|
||||
import OverlayBody from 'src/reusable_ui/components/overlays/OverlayBody'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import DatabasesTemplateBuilder from 'src/tempVars/components/DatabasesTemplateBuilder'
|
||||
import CSVTemplateBuilder from 'src/tempVars/components/CSVTemplateBuilder'
|
||||
import MapTemplateBuilder from 'src/tempVars/components/MapTemplateBuilder'
|
||||
|
@ -31,7 +35,9 @@ import TagKeysTemplateBuilder from 'src/tempVars/components/TagKeysTemplateBuild
|
|||
import TagValuesTemplateBuilder from 'src/tempVars/components/TagValuesTemplateBuilder'
|
||||
import MetaQueryTemplateBuilder from 'src/tempVars/components/MetaQueryTemplateBuilder'
|
||||
import TextTemplateBuilder from 'src/tempVars/components/TextTemplateBuilder'
|
||||
import SourceDropdown from 'src/flux/components/SourceDropdown'
|
||||
|
||||
// Types
|
||||
import {
|
||||
Template,
|
||||
TemplateType,
|
||||
|
@ -40,7 +46,10 @@ import {
|
|||
Source,
|
||||
RemoteDataState,
|
||||
Notification,
|
||||
QueryType,
|
||||
} from 'src/types'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
TEMPLATE_TYPES_LIST,
|
||||
DEFAULT_TEMPLATES,
|
||||
|
@ -53,11 +62,13 @@ interface Props {
|
|||
template?: Template
|
||||
templates: Template[]
|
||||
source: Source
|
||||
sources: Source[]
|
||||
onCancel: () => void
|
||||
onCreate?: (template: Template) => Promise<any>
|
||||
onUpdate?: (template: Template) => Promise<any>
|
||||
onDelete?: () => Promise<any>
|
||||
notify: (n: Notification) => void
|
||||
isDynamicSourceSelected: boolean
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -65,6 +76,8 @@ interface State {
|
|||
isNew: boolean
|
||||
savingStatus: RemoteDataState
|
||||
deletingStatus: RemoteDataState
|
||||
isDynamicSourceSelected: boolean
|
||||
selectedSource: Source
|
||||
}
|
||||
|
||||
const TEMPLATE_BUILDERS = {
|
||||
|
@ -81,14 +94,17 @@ const TEMPLATE_BUILDERS = {
|
|||
|
||||
const DEFAULT_TEMPLATE = DEFAULT_TEMPLATES[TemplateType.Databases]
|
||||
|
||||
const DEFAULT_SOURCE_DATABASE_ID = '0'
|
||||
|
||||
@ErrorHandling
|
||||
class TemplateVariableEditor extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const defaultState = {
|
||||
savingStatus: RemoteDataState.NotStarted,
|
||||
deletingStatus: RemoteDataState.NotStarted,
|
||||
isDynamicSourceSelected: props.isDynamicSourceSelected,
|
||||
selectedSource: props.selectedSource,
|
||||
}
|
||||
|
||||
const {template} = this.props
|
||||
|
@ -109,8 +125,13 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {source, onCancel, notify, templates} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
const {sources, onCancel, notify, templates} = this.props
|
||||
const {
|
||||
isDynamicSourceSelected,
|
||||
selectedSource,
|
||||
nextTemplate,
|
||||
isNew,
|
||||
} = this.state
|
||||
const TemplateBuilder = this.templateBuilder
|
||||
|
||||
return (
|
||||
|
@ -136,7 +157,20 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
</OverlayHeading>
|
||||
<OverlayBody>
|
||||
<div className="faux-form">
|
||||
<div className="form-group col-sm-6">
|
||||
<div className="form-group col-sm-4">
|
||||
<label>Data Source</label>
|
||||
<SourceDropdown
|
||||
onSelectDynamicSource={this.handleSelectDynamicSource}
|
||||
sources={sources}
|
||||
source={selectedSource}
|
||||
isDynamicSourceSelected={isDynamicSourceSelected}
|
||||
onChangeSource={this.handleChangeSource}
|
||||
allowDynamicSource={true}
|
||||
type={QueryType.InfluxQL}
|
||||
widthPixels={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-sm-4">
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -148,7 +182,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
<div className="form-group col-sm-4">
|
||||
<label>Type</label>
|
||||
<Dropdown
|
||||
items={TEMPLATE_TYPES_LIST}
|
||||
|
@ -161,7 +195,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
<TemplateBuilder
|
||||
template={nextTemplate}
|
||||
templates={templates}
|
||||
source={source}
|
||||
source={selectedSource}
|
||||
onUpdateTemplate={this.handleUpdateTemplate}
|
||||
notify={notify}
|
||||
onUpdateDefaultTemplateValue={
|
||||
|
@ -291,7 +325,18 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
return
|
||||
}
|
||||
const {onUpdate, onCreate, notify} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
const {
|
||||
nextTemplate,
|
||||
isNew,
|
||||
isDynamicSourceSelected,
|
||||
selectedSource,
|
||||
} = this.state
|
||||
|
||||
nextTemplate.sourceID = DEFAULT_SOURCE_DATABASE_ID
|
||||
|
||||
if (!isDynamicSourceSelected) {
|
||||
nextTemplate.sourceID = selectedSource.id
|
||||
}
|
||||
|
||||
nextTemplate.tempVar = formatTempVar(nextTemplate.tempVar)
|
||||
|
||||
|
@ -331,7 +376,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
} = this.state
|
||||
|
||||
let canSaveValues = true
|
||||
if (type === TemplateType.CSV && _.isEmpty(values)) {
|
||||
if (type === TemplateType.CSV && isEmpty(values)) {
|
||||
canSaveValues = false
|
||||
}
|
||||
|
||||
|
@ -373,6 +418,22 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
return 'Save'
|
||||
}
|
||||
|
||||
// When we change the source database here and below, we want to reset the template complete
|
||||
private handleSelectDynamicSource = () => {
|
||||
this.setState({
|
||||
isDynamicSourceSelected: true,
|
||||
nextTemplate: DEFAULT_TEMPLATE(),
|
||||
})
|
||||
}
|
||||
|
||||
private handleChangeSource = (source: Source) => {
|
||||
this.setState({
|
||||
isDynamicSourceSelected: false,
|
||||
selectedSource: source,
|
||||
nextTemplate: DEFAULT_TEMPLATE(),
|
||||
})
|
||||
}
|
||||
|
||||
private handleDelete = (): void => {
|
||||
const {onDelete} = this.props
|
||||
|
||||
|
@ -384,6 +445,23 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {notify: notifyActionCreator}
|
||||
const mapDispatchToProps = {
|
||||
notify: notifyActionCreator,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TemplateVariableEditor)
|
||||
const mapStateToProps = (state, props) => {
|
||||
const {sources} = state
|
||||
const sourceID = get(props, 'template.sourceID', DEFAULT_SOURCE_DATABASE_ID)
|
||||
const selectedSource = sources.find(source => source.id === sourceID)
|
||||
const isDynamicSourceSelected =
|
||||
selectedSource === undefined || sourceID === DEFAULT_SOURCE_DATABASE_ID
|
||||
return {
|
||||
sources,
|
||||
isDynamicSourceSelected,
|
||||
selectedSource,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
TemplateVariableEditor
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ import templateReplace, {
|
|||
|
||||
import {resolveValues} from 'src/tempVars/utils'
|
||||
|
||||
import {Template, RemoteDataState} from 'src/types'
|
||||
import {Source, Template, RemoteDataState} from 'src/types'
|
||||
|
||||
type TemplateName = string
|
||||
|
||||
|
@ -240,6 +240,7 @@ export async function hydrateTemplate(
|
|||
|
||||
export async function hydrateTemplates(
|
||||
templates: Template[],
|
||||
sources: Source[],
|
||||
hydrateOptions: HydrateTemplateOptions
|
||||
) {
|
||||
const graph = graphFromTemplates(templates)
|
||||
|
@ -251,10 +252,17 @@ export async function hydrateTemplates(
|
|||
|
||||
node.status = RemoteDataState.Loading
|
||||
|
||||
const templateSource = sources.find(
|
||||
s => s.id === node.initialTemplate.sourceID
|
||||
)
|
||||
const proxyUrl = templateSource
|
||||
? templateSource.links.proxy
|
||||
: hydrateOptions.proxyUrl
|
||||
|
||||
node.hydratedTemplate = await hydrateTemplate(
|
||||
node.initialTemplate,
|
||||
resolvedTemplates,
|
||||
hydrateOptions
|
||||
{...hydrateOptions, proxyUrl}
|
||||
)
|
||||
|
||||
node.status = RemoteDataState.Done
|
||||
|
|
|
@ -54,6 +54,7 @@ export interface Template {
|
|||
type: TemplateType
|
||||
label: string
|
||||
query?: TemplateQuery
|
||||
sourceID?: string
|
||||
}
|
||||
|
||||
export interface TemplateUpdate {
|
||||
|
|
|
@ -382,7 +382,7 @@ describe('hydrateTemplates', () => {
|
|||
},
|
||||
}
|
||||
|
||||
const result = await hydrateTemplates(templates, {fetcher: fakeFetcher})
|
||||
const result = await hydrateTemplates(templates, [], {fetcher: fakeFetcher})
|
||||
|
||||
expect(result.find(t => t.id === 'a').values).toContainEqual({
|
||||
value: 'success',
|
||||
|
|
Loading…
Reference in New Issue