import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { cloneDeep, defaults as lodashDefaults } from 'lodash'; import { LoadingState, VariableType } from '@grafana/data'; import { variableAdapters } from '../adapters'; import { changeVariableNameSucceeded } from '../editor/reducer'; import { VariableModel, VariableOption, VariableWithOptions } from '../types'; import { ensureStringValues } from '../utils'; import { getInstanceState, getNextVariableIndex } from './selectors'; import { AddVariable, initialVariablesState, VariablePayload, VariablesState } from './types'; const sharedReducerSlice = createSlice({ name: 'templating/shared', initialState: initialVariablesState, reducers: { addVariable: (state: VariablesState, action: PayloadAction>) => { const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id const adapter = variableAdapters.get(action.payload.type); const initialState = cloneDeep(adapter.initialState); const model = adapter.beforeAdding ? adapter.beforeAdding(action.payload.data.model) : cloneDeep(action.payload.data.model); const variable = { ...lodashDefaults({}, model, initialState), id: id, index: action.payload.data.index, global: action.payload.data.global, }; state[id] = variable; }, variableStateNotStarted: (state: VariablesState, action: PayloadAction) => { const instanceState = getInstanceState(state, action.payload.id); instanceState.state = LoadingState.NotStarted; instanceState.error = null; }, variableStateFetching: (state: VariablesState, action: PayloadAction) => { const instanceState = getInstanceState(state, action.payload.id); instanceState.state = LoadingState.Loading; instanceState.error = null; }, variableStateCompleted: (state: VariablesState, action: PayloadAction) => { const instanceState = getInstanceState(state, action.payload.id); if (!instanceState) { // we might have cancelled a batch so then this state has been removed return; } instanceState.state = LoadingState.Done; instanceState.error = null; }, variableStateFailed: (state: VariablesState, action: PayloadAction>) => { const instanceState = getInstanceState(state, action.payload.id); if (!instanceState) { // we might have cancelled a batch so then this state has been removed return; } instanceState.state = LoadingState.Error; instanceState.error = action.payload.data.error; }, removeVariable: (state: VariablesState, action: PayloadAction>) => { delete state[action.payload.id]; if (!action.payload.data.reIndex) { return; } const variableStates = Object.values(state); for (let index = 0; index < variableStates.length; index++) { variableStates[index].index = index; } }, duplicateVariable: (state: VariablesState, action: PayloadAction>) => { const original = cloneDeep(state[action.payload.id]); const name = `copy_of_${original.name}`; const newId = action.payload.data?.newId ?? name; const index = getNextVariableIndex(Object.values(state)); state[newId] = { ...cloneDeep(variableAdapters.get(action.payload.type).initialState), ...original, id: newId, name, index, }; }, changeVariableOrder: ( state: VariablesState, action: PayloadAction> ) => { const { toIndex, fromIndex } = action.payload.data; const variableStates = Object.values(state); for (let index = 0; index < variableStates.length; index++) { const variable = variableStates[index]; if (variable.index === fromIndex) { variable.index = toIndex; } else if (variable.index > fromIndex && variable.index <= toIndex) { variable.index--; } else if (variable.index < fromIndex && variable.index >= toIndex) { variable.index++; } } }, changeVariableType: (state: VariablesState, action: PayloadAction>) => { const { id } = action.payload; const { label, name, index, description, rootStateKey } = state[id]; state[id] = { ...cloneDeep(variableAdapters.get(action.payload.data.newType).initialState), id, rootStateKey: rootStateKey, label, name, index, description, }; }, setCurrentVariableValue: ( state: VariablesState, action: PayloadAction> ) => { if (!action.payload.data.option) { return; } const instanceState = getInstanceState(state, action.payload.id); const { option } = action.payload.data; const current = { ...option, text: ensureStringValues(option?.text), value: ensureStringValues(option?.value) }; instanceState.current = current; instanceState.options = instanceState.options.map((option) => { option.value = ensureStringValues(option.value); option.text = ensureStringValues(option.text); let selected = false; if (Array.isArray(current.value)) { for (let index = 0; index < current.value.length; index++) { const value = current.value[index]; if (option.value === value) { selected = true; break; } } } else if (option.value === current.value) { selected = true; } option.selected = selected; return option; }); }, changeVariableProp: ( state: VariablesState, action: PayloadAction> ) => { const instanceState = getInstanceState(state, action.payload.id); (instanceState as Record)[action.payload.data.propName] = action.payload.data.propValue; }, }, extraReducers: (builder) => builder.addCase(changeVariableNameSucceeded, (state, action) => { const instanceState = getInstanceState(state, action.payload.id); instanceState.name = action.payload.data.newName; }), }); export const sharedReducer = sharedReducerSlice.reducer; export const { removeVariable, addVariable, changeVariableProp, changeVariableOrder, duplicateVariable, setCurrentVariableValue, changeVariableType, variableStateNotStarted, variableStateFetching, variableStateCompleted, variableStateFailed, } = sharedReducerSlice.actions;