sharedReducer.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { createSlice, PayloadAction } from '@reduxjs/toolkit';
  2. import { cloneDeep, defaults as lodashDefaults } from 'lodash';
  3. import { LoadingState, VariableType } from '@grafana/data';
  4. import { variableAdapters } from '../adapters';
  5. import { changeVariableNameSucceeded } from '../editor/reducer';
  6. import { VariableModel, VariableOption, VariableWithOptions } from '../types';
  7. import { ensureStringValues } from '../utils';
  8. import { getInstanceState, getNextVariableIndex } from './selectors';
  9. import { AddVariable, initialVariablesState, VariablePayload, VariablesState } from './types';
  10. const sharedReducerSlice = createSlice({
  11. name: 'templating/shared',
  12. initialState: initialVariablesState,
  13. reducers: {
  14. addVariable: (state: VariablesState, action: PayloadAction<VariablePayload<AddVariable>>) => {
  15. const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id
  16. const adapter = variableAdapters.get(action.payload.type);
  17. const initialState = cloneDeep(adapter.initialState);
  18. const model = adapter.beforeAdding
  19. ? adapter.beforeAdding(action.payload.data.model)
  20. : cloneDeep(action.payload.data.model);
  21. const variable = {
  22. ...lodashDefaults({}, model, initialState),
  23. id: id,
  24. index: action.payload.data.index,
  25. global: action.payload.data.global,
  26. };
  27. state[id] = variable;
  28. },
  29. variableStateNotStarted: (state: VariablesState, action: PayloadAction<VariablePayload>) => {
  30. const instanceState = getInstanceState(state, action.payload.id);
  31. instanceState.state = LoadingState.NotStarted;
  32. instanceState.error = null;
  33. },
  34. variableStateFetching: (state: VariablesState, action: PayloadAction<VariablePayload>) => {
  35. const instanceState = getInstanceState(state, action.payload.id);
  36. instanceState.state = LoadingState.Loading;
  37. instanceState.error = null;
  38. },
  39. variableStateCompleted: (state: VariablesState, action: PayloadAction<VariablePayload>) => {
  40. const instanceState = getInstanceState(state, action.payload.id);
  41. if (!instanceState) {
  42. // we might have cancelled a batch so then this state has been removed
  43. return;
  44. }
  45. instanceState.state = LoadingState.Done;
  46. instanceState.error = null;
  47. },
  48. variableStateFailed: (state: VariablesState, action: PayloadAction<VariablePayload<{ error: any }>>) => {
  49. const instanceState = getInstanceState(state, action.payload.id);
  50. if (!instanceState) {
  51. // we might have cancelled a batch so then this state has been removed
  52. return;
  53. }
  54. instanceState.state = LoadingState.Error;
  55. instanceState.error = action.payload.data.error;
  56. },
  57. removeVariable: (state: VariablesState, action: PayloadAction<VariablePayload<{ reIndex: boolean }>>) => {
  58. delete state[action.payload.id];
  59. if (!action.payload.data.reIndex) {
  60. return;
  61. }
  62. const variableStates = Object.values(state);
  63. for (let index = 0; index < variableStates.length; index++) {
  64. variableStates[index].index = index;
  65. }
  66. },
  67. duplicateVariable: (state: VariablesState, action: PayloadAction<VariablePayload<{ newId: string }>>) => {
  68. const original = cloneDeep<VariableModel>(state[action.payload.id]);
  69. const name = `copy_of_${original.name}`;
  70. const newId = action.payload.data?.newId ?? name;
  71. const index = getNextVariableIndex(Object.values(state));
  72. state[newId] = {
  73. ...cloneDeep(variableAdapters.get(action.payload.type).initialState),
  74. ...original,
  75. id: newId,
  76. name,
  77. index,
  78. };
  79. },
  80. changeVariableOrder: (
  81. state: VariablesState,
  82. action: PayloadAction<VariablePayload<{ fromIndex: number; toIndex: number }>>
  83. ) => {
  84. const { toIndex, fromIndex } = action.payload.data;
  85. const variableStates = Object.values(state);
  86. for (let index = 0; index < variableStates.length; index++) {
  87. const variable = variableStates[index];
  88. if (variable.index === fromIndex) {
  89. variable.index = toIndex;
  90. } else if (variable.index > fromIndex && variable.index <= toIndex) {
  91. variable.index--;
  92. } else if (variable.index < fromIndex && variable.index >= toIndex) {
  93. variable.index++;
  94. }
  95. }
  96. },
  97. changeVariableType: (state: VariablesState, action: PayloadAction<VariablePayload<{ newType: VariableType }>>) => {
  98. const { id } = action.payload;
  99. const { label, name, index, description, rootStateKey } = state[id];
  100. state[id] = {
  101. ...cloneDeep(variableAdapters.get(action.payload.data.newType).initialState),
  102. id,
  103. rootStateKey: rootStateKey,
  104. label,
  105. name,
  106. index,
  107. description,
  108. };
  109. },
  110. setCurrentVariableValue: (
  111. state: VariablesState,
  112. action: PayloadAction<VariablePayload<{ option: VariableOption | undefined }>>
  113. ) => {
  114. if (!action.payload.data.option) {
  115. return;
  116. }
  117. const instanceState = getInstanceState<VariableWithOptions>(state, action.payload.id);
  118. const { option } = action.payload.data;
  119. const current = { ...option, text: ensureStringValues(option?.text), value: ensureStringValues(option?.value) };
  120. instanceState.current = current;
  121. instanceState.options = instanceState.options.map((option) => {
  122. option.value = ensureStringValues(option.value);
  123. option.text = ensureStringValues(option.text);
  124. let selected = false;
  125. if (Array.isArray(current.value)) {
  126. for (let index = 0; index < current.value.length; index++) {
  127. const value = current.value[index];
  128. if (option.value === value) {
  129. selected = true;
  130. break;
  131. }
  132. }
  133. } else if (option.value === current.value) {
  134. selected = true;
  135. }
  136. option.selected = selected;
  137. return option;
  138. });
  139. },
  140. changeVariableProp: (
  141. state: VariablesState,
  142. action: PayloadAction<VariablePayload<{ propName: string; propValue: any }>>
  143. ) => {
  144. const instanceState = getInstanceState(state, action.payload.id);
  145. (instanceState as Record<string, any>)[action.payload.data.propName] = action.payload.data.propValue;
  146. },
  147. },
  148. extraReducers: (builder) =>
  149. builder.addCase(changeVariableNameSucceeded, (state, action) => {
  150. const instanceState = getInstanceState(state, action.payload.id);
  151. instanceState.name = action.payload.data.newName;
  152. }),
  153. });
  154. export const sharedReducer = sharedReducerSlice.reducer;
  155. export const {
  156. removeVariable,
  157. addVariable,
  158. changeVariableProp,
  159. changeVariableOrder,
  160. duplicateVariable,
  161. setCurrentVariableValue,
  162. changeVariableType,
  163. variableStateNotStarted,
  164. variableStateFetching,
  165. variableStateCompleted,
  166. variableStateFailed,
  167. } = sharedReducerSlice.actions;