mirror of
https://github.com/apache/superset.git
synced 2026-05-11 10:55:43 +00:00
feat: Add confirmation modal for unsaved changes (#33809)
This commit is contained in:
committed by
GitHub
parent
050ccdcb3d
commit
057218d87f
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { JsonObject } from '@superset-ui/core';
|
||||
import { alterForComparison, getChartFormDiffs, isEqualish } from '.';
|
||||
|
||||
jest.mock('../sanitizeFormData', () => ({
|
||||
sanitizeFormData: (fd: JsonObject): JsonObject => ({
|
||||
...fd,
|
||||
_sanitized: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('alterForComparison', () => {
|
||||
it.each([
|
||||
[null, null],
|
||||
['', null],
|
||||
[[], null],
|
||||
[{}, null],
|
||||
[
|
||||
[1, 2],
|
||||
[1, 2],
|
||||
],
|
||||
[{ a: 1 }, { a: 1 }],
|
||||
['foo', 'foo'],
|
||||
])('normalizes %p to %p', (input, expected) => {
|
||||
expect(alterForComparison(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEqualish', () => {
|
||||
it('returns true for semantically equal values with different formats', () => {
|
||||
expect(isEqualish('', null)).toBe(true);
|
||||
expect(isEqualish([], null)).toBe(true);
|
||||
expect(isEqualish({}, null)).toBe(true);
|
||||
expect(isEqualish([1], [1])).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for clearly different values', () => {
|
||||
expect(isEqualish([1], [2])).toBe(false);
|
||||
expect(isEqualish({ a: 1 }, { a: 2 })).toBe(false);
|
||||
expect(isEqualish('foo', 'bar')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChartFormDiffs', () => {
|
||||
it('returns diffs for changed values', () => {
|
||||
const original = { metric: 'count', adhoc_filters: [] };
|
||||
const current = { metric: 'sum__num', adhoc_filters: [] };
|
||||
|
||||
const diffs = getChartFormDiffs(original, current);
|
||||
|
||||
expect(diffs).toHaveProperty('metric');
|
||||
expect(diffs.metric).toEqual({
|
||||
before: 'count',
|
||||
after: 'sum__num',
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores noisy keys', () => {
|
||||
const original = { where: 'a = 1', metric: 'count' };
|
||||
const current = { where: 'a = 2', metric: 'sum__num' };
|
||||
|
||||
const diffs = getChartFormDiffs(original, current);
|
||||
|
||||
expect(diffs).not.toHaveProperty('where');
|
||||
expect(diffs).toHaveProperty('metric');
|
||||
});
|
||||
|
||||
it('does not include values that are equalish', () => {
|
||||
const original = { filters: [], metric: 'count' };
|
||||
const current = { filters: null, metric: 'count' };
|
||||
|
||||
const diffs = getChartFormDiffs(original, current);
|
||||
|
||||
expect(diffs).toEqual({});
|
||||
});
|
||||
|
||||
it('handles missing keys in original or current gracefully', () => {
|
||||
const original = { metric: 'count' };
|
||||
const current = { metric: 'count', new_field: 'value' };
|
||||
|
||||
const diffs = getChartFormDiffs(original, current);
|
||||
|
||||
expect(diffs).toHaveProperty('new_field');
|
||||
expect(diffs.new_field).toEqual({
|
||||
before: undefined,
|
||||
after: 'value',
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores keys that are missing in current and not explicitly changed', () => {
|
||||
const original = { metric: 'count', removed_field: 'gone' };
|
||||
const current = { metric: 'count' };
|
||||
|
||||
const diffs = getChartFormDiffs(original, current);
|
||||
|
||||
expect(diffs).not.toHaveProperty('removed_field');
|
||||
});
|
||||
});
|
||||
66
superset-frontend/src/utils/getChartFormDiffs/index.ts
Normal file
66
superset-frontend/src/utils/getChartFormDiffs/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isEqual } from 'lodash';
|
||||
import { DiffType } from 'src/types/DiffType';
|
||||
import { JsonObject } from '@superset-ui/core';
|
||||
import { sanitizeFormData } from '../sanitizeFormData';
|
||||
|
||||
export const noisyKeys = new Set(['filters', 'having', 'where']);
|
||||
|
||||
export const alterForComparison = (value: unknown): unknown => {
|
||||
if (value == null || value === '') return null;
|
||||
if (Array.isArray(value) && value.length === 0) return null;
|
||||
if (typeof value === 'object' && value && Object.keys(value).length === 0)
|
||||
return null;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const isEqualish = (a: unknown, b: unknown): boolean =>
|
||||
isEqual(alterForComparison(a), alterForComparison(b));
|
||||
|
||||
export const getChartFormDiffs = (
|
||||
originalFormData: Record<string, unknown>,
|
||||
currentFormData: Record<string, unknown>,
|
||||
): Record<string, DiffType> => {
|
||||
const ofd: JsonObject = sanitizeFormData(originalFormData);
|
||||
const cfd: JsonObject = sanitizeFormData(currentFormData);
|
||||
|
||||
const keys = new Set([...Object.keys(ofd), ...Object.keys(cfd)]);
|
||||
const diffs: Record<string, DiffType> = {};
|
||||
|
||||
keys.forEach((key: string) => {
|
||||
if (noisyKeys.has(key)) return;
|
||||
|
||||
const original = ofd[key];
|
||||
const current = cfd[key];
|
||||
|
||||
const currentHasKey = Object.prototype.hasOwnProperty.call(cfd, key);
|
||||
const originalHasKey = Object.prototype.hasOwnProperty.call(ofd, key);
|
||||
|
||||
const bothExplicit = currentHasKey && originalHasKey;
|
||||
|
||||
if (!bothExplicit && !currentHasKey) return;
|
||||
|
||||
if (!isEqualish(original, current))
|
||||
diffs[key] = { before: original, after: current };
|
||||
});
|
||||
|
||||
return diffs;
|
||||
};
|
||||
25
superset-frontend/src/utils/sanitizeFormData/index.ts
Normal file
25
superset-frontend/src/utils/sanitizeFormData/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { JsonObject } from '@superset-ui/core';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
const TEMPORARY_CONTROLS: string[] = ['url_params'];
|
||||
|
||||
export const sanitizeFormData = (formData: JsonObject): JsonObject =>
|
||||
omit(formData, TEMPORARY_CONTROLS);
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { sanitizeFormData } from '.';
|
||||
|
||||
test('sanitizeFormData removes temporary control values', () => {
|
||||
expect(
|
||||
sanitizeFormData({
|
||||
url_params: { foo: 'bar' },
|
||||
metrics: ['foo', 'bar'],
|
||||
}),
|
||||
).toEqual({ metrics: ['foo', 'bar'] });
|
||||
});
|
||||
Reference in New Issue
Block a user