From 418059bc23a53b63ff9cdd534967a2c36a509018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 2 Nov 2021 06:26:16 +0100 Subject: [PATCH] Variables: Adds change tracking (#41143) * Variables: Adds change tracking * Chore: fix strict error * Chore: removes updating value from isDirty check --- .../app/features/variables/state/selectors.ts | 4 ++ .../state/transactionReducer.test.ts | 53 +++++++++++++++++++ .../variables/state/transactionReducer.ts | 35 +++++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/public/app/features/variables/state/selectors.ts b/public/app/features/variables/state/selectors.ts index 3322b775137..c8a306ebbab 100644 --- a/public/app/features/variables/state/selectors.ts +++ b/public/app/features/variables/state/selectors.ts @@ -49,3 +49,7 @@ export type GetVariables = typeof getVariables; export const getNewVariabelIndex = (state: StoreState = getState()): number => { return Object.values(state.templating.variables).length; }; + +export function getVariablesIsDirty(state: StoreState = getState()): boolean { + return state.templating.transaction.isDirty; +} diff --git a/public/app/features/variables/state/transactionReducer.test.ts b/public/app/features/variables/state/transactionReducer.test.ts index 9632e9f7cac..34a9f8d495c 100644 --- a/public/app/features/variables/state/transactionReducer.test.ts +++ b/public/app/features/variables/state/transactionReducer.test.ts @@ -1,4 +1,5 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { removeVariable, variableStateNotStarted } from './sharedReducer'; import { initialTransactionState, transactionReducer, @@ -59,4 +60,56 @@ describe('transactionReducer', () => { .thenStateShouldEqual({ ...initialTransactionState }); }); }); + + describe('extraReducers', () => { + describe('isDirty', () => { + describe('when called during fetch', () => { + it('then isDirty should not be changed', () => { + reducerTester() + .givenReducer(transactionReducer, { + ...initialTransactionState, + status: TransactionStatus.Fetching, + }) + .whenActionIsDispatched(removeVariable({} as any)) + .thenStateShouldEqual({ uid: null, status: TransactionStatus.Fetching, isDirty: false }); + }); + }); + + describe('when called after clean', () => { + it('then isDirty should not be changed', () => { + reducerTester() + .givenReducer(transactionReducer, { + ...initialTransactionState, + status: TransactionStatus.NotStarted, + }) + .whenActionIsDispatched(removeVariable({} as any)) + .thenStateShouldEqual({ uid: null, status: TransactionStatus.NotStarted, isDirty: false }); + }); + }); + + describe('when called after complete with action that affects isDirty', () => { + it('then isDirty should be changed', () => { + reducerTester() + .givenReducer(transactionReducer, { + ...initialTransactionState, + status: TransactionStatus.Completed, + }) + .whenActionIsDispatched(removeVariable({} as any)) + .thenStateShouldEqual({ uid: null, status: TransactionStatus.Completed, isDirty: true }); + }); + }); + + describe('when called after complete with action that does not affect isDirty', () => { + it('then isDirty should be changed', () => { + reducerTester() + .givenReducer(transactionReducer, { + ...initialTransactionState, + status: TransactionStatus.Completed, + }) + .whenActionIsDispatched(variableStateNotStarted({} as any)) + .thenStateShouldEqual({ uid: null, status: TransactionStatus.Completed, isDirty: false }); + }); + }); + }); + }); }); diff --git a/public/app/features/variables/state/transactionReducer.ts b/public/app/features/variables/state/transactionReducer.ts index 7cf116a9d23..3258c123397 100644 --- a/public/app/features/variables/state/transactionReducer.ts +++ b/public/app/features/variables/state/transactionReducer.ts @@ -1,4 +1,12 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { + addVariable, + changeVariableOrder, + changeVariableProp, + changeVariableType, + duplicateVariable, + removeVariable, +} from './sharedReducer'; export enum TransactionStatus { NotStarted = 'Not started', @@ -9,9 +17,14 @@ export enum TransactionStatus { export interface TransactionState { uid: string | undefined | null; status: TransactionStatus; + isDirty: boolean; } -export const initialTransactionState: TransactionState = { uid: null, status: TransactionStatus.NotStarted }; +export const initialTransactionState: TransactionState = { + uid: null, + status: TransactionStatus.NotStarted, + isDirty: false, +}; const transactionSlice = createSlice({ name: 'templating/transaction', @@ -32,10 +45,28 @@ const transactionSlice = createSlice({ variablesClearTransaction: (state, action: PayloadAction) => { state.uid = null; state.status = TransactionStatus.NotStarted; + state.isDirty = false; }, }, + extraReducers: (builder) => + builder.addMatcher(actionAffectsDirtyState, (state, action) => { + if (state.status === TransactionStatus.Completed) { + state.isDirty = true; + } + }), }); +function actionAffectsDirtyState(action: AnyAction): boolean { + return [ + removeVariable.type, + addVariable.type, + changeVariableProp.type, + changeVariableOrder.type, + duplicateVariable.type, + changeVariableType.type, + ].includes(action.type); +} + export const { variablesInitTransaction, variablesClearTransaction,