mirror of
https://github.com/apache/superset.git
synced 2026-04-19 16:14:52 +00:00
fix(alerts): fix error toast when editing report with saved tab selection (#38198)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,9 @@ import {
|
||||
userEvent,
|
||||
waitFor,
|
||||
within,
|
||||
createStore,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||
import { buildErrorTooltipMessage } from './buildErrorTooltipMessage';
|
||||
import AlertReportModal, { AlertReportModalProps } from './AlertReportModal';
|
||||
import { AlertObject, NotificationMethodOption } from './types';
|
||||
@@ -109,8 +111,16 @@ const FETCH_REPORT_WITH_FILTERS_ENDPOINT = 'glob:*/api/v1/report/3';
|
||||
const FETCH_REPORT_NO_FILTER_NAME_ENDPOINT = 'glob:*/api/v1/report/4';
|
||||
const FETCH_REPORT_OVERWRITE_ENDPOINT = 'glob:*/api/v1/report/5';
|
||||
|
||||
fetchMock.get(FETCH_DASHBOARD_ENDPOINT, { result: generateMockPayload(true) });
|
||||
fetchMock.get(FETCH_CHART_ENDPOINT, { result: generateMockPayload(false) });
|
||||
fetchMock.get(
|
||||
FETCH_DASHBOARD_ENDPOINT,
|
||||
{ result: generateMockPayload(true) },
|
||||
{ name: FETCH_DASHBOARD_ENDPOINT },
|
||||
);
|
||||
fetchMock.get(
|
||||
FETCH_CHART_ENDPOINT,
|
||||
{ result: generateMockPayload(false) },
|
||||
{ name: FETCH_CHART_ENDPOINT },
|
||||
);
|
||||
fetchMock.get(FETCH_REPORT_WITH_FILTERS_ENDPOINT, {
|
||||
result: {
|
||||
...generateMockPayload(true),
|
||||
@@ -839,7 +849,7 @@ test('filter reappears in dropdown after clearing with X icon', async () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'clear-icon-tabs' },
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
|
||||
fetchMock.post(
|
||||
@@ -902,6 +912,457 @@ test('filter reappears in dropdown after clearing with X icon', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
const setupAnchorMocks = (
|
||||
nativeFilters: Record<string, unknown>,
|
||||
anchor = 'TAB-abc',
|
||||
tabsOverride?: {
|
||||
all_tabs: Record<string, string>;
|
||||
tab_tree: { title: string; value: string }[];
|
||||
},
|
||||
) => {
|
||||
const payloadWithAnchor = {
|
||||
...generateMockPayload(true),
|
||||
extra: { dashboard: { anchor } },
|
||||
};
|
||||
|
||||
const defaultTabs = {
|
||||
all_tabs: { [anchor]: `Tab ${anchor}` },
|
||||
tab_tree: [{ title: `Tab ${anchor}`, value: anchor }],
|
||||
};
|
||||
const tabs = tabsOverride ?? defaultTabs;
|
||||
|
||||
// Clear call history so waitFor assertions don't match calls from prior tests.
|
||||
fetchMock.callHistory.clear();
|
||||
|
||||
// Only replace the named routes that need anchor-specific overrides;
|
||||
// unnamed related-endpoint routes (owners, database, etc.) stay intact.
|
||||
fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
|
||||
fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
|
||||
fetchMock.get(
|
||||
FETCH_DASHBOARD_ENDPOINT,
|
||||
{ result: payloadWithAnchor },
|
||||
{ name: FETCH_DASHBOARD_ENDPOINT },
|
||||
);
|
||||
fetchMock.get(
|
||||
FETCH_CHART_ENDPOINT,
|
||||
{ result: generateMockPayload(false) },
|
||||
{ name: FETCH_CHART_ENDPOINT },
|
||||
);
|
||||
fetchMock.get(
|
||||
tabsEndpoint,
|
||||
{
|
||||
result: {
|
||||
...tabs,
|
||||
native_filters: nativeFilters,
|
||||
},
|
||||
},
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
};
|
||||
|
||||
const restoreAnchorMocks = () => {
|
||||
fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
|
||||
fetchMock.get(
|
||||
FETCH_DASHBOARD_ENDPOINT,
|
||||
{ result: generateMockPayload(true) },
|
||||
{ name: FETCH_DASHBOARD_ENDPOINT },
|
||||
);
|
||||
fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
|
||||
fetchMock.get(
|
||||
FETCH_CHART_ENDPOINT,
|
||||
{ result: generateMockPayload(false) },
|
||||
{ name: FETCH_CHART_ENDPOINT },
|
||||
);
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
fetchMock.get(
|
||||
tabsEndpoint,
|
||||
{ result: { all_tabs: {}, tab_tree: [] } },
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
};
|
||||
|
||||
test('no error toast when anchor tab has no scoped native filters', async () => {
|
||||
setupAnchorMocks({
|
||||
all: [
|
||||
{
|
||||
id: 'NATIVE_FILTER-1',
|
||||
name: 'Filter 1',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
fetchMock.callHistory
|
||||
.calls()
|
||||
.some(c => c.url.includes('/dashboard/1/tabs')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('no error toast when anchor tab set and dashboard has zero native filters', async () => {
|
||||
setupAnchorMocks({});
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
fetchMock.callHistory
|
||||
.calls()
|
||||
.some(c => c.url.includes('/dashboard/1/tabs')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('stale JSON array anchor is cleared without crash or toast', async () => {
|
||||
const staleAnchor = JSON.stringify(['TAB-abc', 'TAB-missing']);
|
||||
setupAnchorMocks(
|
||||
{
|
||||
all: [
|
||||
{
|
||||
id: 'NATIVE_FILTER-1',
|
||||
name: 'Filter 1',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
staleAnchor,
|
||||
{
|
||||
all_tabs: { 'TAB-abc': 'Tab ABC' },
|
||||
tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
|
||||
},
|
||||
);
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
// Wait for the tabs useEffect to process the stale anchor
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
fetchMock.callHistory
|
||||
.calls()
|
||||
.some(c => c.url.includes('/dashboard/1/tabs')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// No error toast dispatched (the .then() handler ran without crashing)
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
// Verify anchor was cleared at the payload level: trigger save and
|
||||
// inspect the PUT body to confirm extra.dashboard.anchor is undefined
|
||||
const updateEndpoint = 'glob:*/api/v1/report/1';
|
||||
fetchMock.put(
|
||||
updateEndpoint,
|
||||
{ id: 1, result: {} },
|
||||
{ name: 'put-report-1' },
|
||||
);
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: /save/i });
|
||||
expect(saveButton).not.toBeDisabled();
|
||||
userEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const putCalls = fetchMock.callHistory
|
||||
.calls()
|
||||
.filter(
|
||||
c => c.url.includes('/api/v1/report/') && c.options?.method === 'put',
|
||||
);
|
||||
expect(putCalls).toHaveLength(1);
|
||||
});
|
||||
|
||||
const putCall = fetchMock.callHistory
|
||||
.calls()
|
||||
.find(
|
||||
c => c.url.includes('/api/v1/report/') && c.options?.method === 'put',
|
||||
);
|
||||
const body = JSON.parse(putCall!.options?.body as string);
|
||||
expect(body.extra.dashboard.anchor).toBeUndefined();
|
||||
} finally {
|
||||
fetchMock.removeRoute('put-report-1');
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('tabs API failure shows danger toast via Redux store', async () => {
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
fetchMock.get(tabsEndpoint, 500, { name: tabsEndpoint });
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
|
||||
await waitFor(() => {
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
} finally {
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
fetchMock.get(
|
||||
tabsEndpoint,
|
||||
{ result: { all_tabs: {}, tab_tree: [] } },
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('null all_tabs does not crash or show error toast', async () => {
|
||||
setupAnchorMocks({
|
||||
all: [
|
||||
{
|
||||
id: 'NATIVE_FILTER-1',
|
||||
name: 'Filter 1',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Override tabs endpoint with null all_tabs
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
fetchMock.get(
|
||||
tabsEndpoint,
|
||||
{
|
||||
result: {
|
||||
all_tabs: null,
|
||||
tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
|
||||
native_filters: {
|
||||
all: [
|
||||
{
|
||||
id: 'NATIVE_FILTER-1',
|
||||
name: 'Filter 1',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
// Wait for tabs useEffect to complete
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
fetchMock.callHistory
|
||||
.calls()
|
||||
.some(c => c.url.includes('/dashboard/1/tabs')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// No error toast dispatched
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
// Component remains interactive
|
||||
expect(
|
||||
screen.getByRole('combobox', { name: /select filter/i }),
|
||||
).toBeInTheDocument();
|
||||
} finally {
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('missing native_filters in tabs response does not crash or show error toast', async () => {
|
||||
setupAnchorMocks({});
|
||||
|
||||
// Override tabs endpoint with no native_filters key
|
||||
fetchMock.removeRoute(tabsEndpoint);
|
||||
fetchMock.get(
|
||||
tabsEndpoint,
|
||||
{
|
||||
result: {
|
||||
all_tabs: { 'TAB-abc': 'Tab ABC' },
|
||||
tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
|
||||
},
|
||||
},
|
||||
{ name: tabsEndpoint },
|
||||
);
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
// Wait for tabs useEffect to complete
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
fetchMock.callHistory
|
||||
.calls()
|
||||
.some(c => c.url.includes('/dashboard/1/tabs')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// No error toast dispatched
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('anchor tab with scoped filters loads filter options correctly', async () => {
|
||||
// Use JSON-parseable non-array anchor to exercise the scoped filter
|
||||
// code path at line 1108 (JSON.parse('42') → 42, not an array)
|
||||
setupAnchorMocks(
|
||||
{
|
||||
all: [
|
||||
{
|
||||
id: 'NATIVE_FILTER-1',
|
||||
name: 'Global Filter',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
'42': [
|
||||
{
|
||||
id: 'NATIVE_FILTER-2',
|
||||
name: 'Tab Scoped Filter',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ column: { name: 'col2' } }],
|
||||
adhoc_filters: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
'42',
|
||||
);
|
||||
|
||||
const store = createStore({}, reducerIndex);
|
||||
|
||||
try {
|
||||
render(<AlertReportModal {...generateMockedProps(true, true)} />, {
|
||||
store,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByTestId('contents-panel'));
|
||||
await screen.findByText(/test dashboard/i);
|
||||
|
||||
const filterDropdown = screen.getByRole('combobox', {
|
||||
name: /select filter/i,
|
||||
});
|
||||
userEvent.click(filterDropdown);
|
||||
|
||||
const filterOption = await screen.findByRole('option', {
|
||||
name: /Tab Scoped Filter/,
|
||||
});
|
||||
expect(filterOption).toBeInTheDocument();
|
||||
|
||||
const toasts = (store.getState() as Record<string, unknown>)
|
||||
.messageToasts as { text: string }[];
|
||||
expect(
|
||||
toasts.some(
|
||||
(toast: { text: string }) =>
|
||||
toast.text === 'There was an error retrieving dashboard tabs.',
|
||||
),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
restoreAnchorMocks();
|
||||
}
|
||||
});
|
||||
|
||||
test('edit mode shows friendly filter names instead of raw IDs', async () => {
|
||||
const props = generateMockedProps(true, true);
|
||||
const editProps = {
|
||||
|
||||
@@ -87,6 +87,7 @@ import {
|
||||
ContentType,
|
||||
ExtraNativeFilter,
|
||||
NativeFilterObject,
|
||||
DashboardTabsResponse,
|
||||
} from 'src/features/alerts/types';
|
||||
import { StatusMessage } from 'src/filters/components/common';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -526,7 +527,9 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
label: string;
|
||||
}[]
|
||||
>([]);
|
||||
const [tabNativeFilters, setTabNativeFilters] = useState<object>({});
|
||||
const [tabNativeFilters, setTabNativeFilters] = useState<
|
||||
Partial<Record<string, NativeFilterObject[]>>
|
||||
>({});
|
||||
const [nativeFilterData, setNativeFilterData] = useState<ExtraNativeFilter[]>(
|
||||
[
|
||||
{
|
||||
@@ -679,9 +682,9 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
const fetchDashboardFilterValues = async (
|
||||
dashboardId: number | string | undefined,
|
||||
columnName: string,
|
||||
datasetId: number | string,
|
||||
datasetId: number | string | null,
|
||||
vizType = 'filter_select',
|
||||
adhocFilters = [],
|
||||
adhocFilters: any[] = [],
|
||||
) => {
|
||||
if (vizType === 'filter_time') {
|
||||
return;
|
||||
@@ -742,7 +745,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
nativeFilterData.map(nativeFilter => {
|
||||
if (!nativeFilter.nativeFilterId) return;
|
||||
const filter = nativeFilters.filter(
|
||||
(f: any) => f.id === nativeFilter.nativeFilterId,
|
||||
f => f.id === nativeFilter.nativeFilterId,
|
||||
)[0];
|
||||
|
||||
const { datasetId } = filter.targets[0];
|
||||
@@ -1079,10 +1082,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
tab_tree: tabTree,
|
||||
all_tabs: allTabs,
|
||||
native_filters: nativeFilters,
|
||||
} = response.json.result;
|
||||
const allTabsWithOrder = tabTree.map(
|
||||
(tab: { value: string }) => tab.value,
|
||||
);
|
||||
}: DashboardTabsResponse = response.json.result;
|
||||
const allTabsWithOrder = tabTree.map(tab => tab.value);
|
||||
|
||||
// Only show all tabs when there are more than one tab
|
||||
if (allTabsWithOrder.length > 1) {
|
||||
@@ -1094,14 +1095,14 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
}
|
||||
|
||||
setTabOptions(tabTree);
|
||||
setTabNativeFilters(nativeFilters);
|
||||
setTabNativeFilters(nativeFilters ?? {});
|
||||
|
||||
if (isEditMode && nativeFilters.all) {
|
||||
if (isEditMode && nativeFilters?.all) {
|
||||
// update options for all filters
|
||||
addNativeFilterOptions(nativeFilters.all);
|
||||
// Also set the available filter options for the add button
|
||||
setNativeFilterOptions(
|
||||
nativeFilters.all.map((filter: any) => ({
|
||||
nativeFilters.all.map(filter => ({
|
||||
value: filter.id,
|
||||
label: filter.name,
|
||||
})),
|
||||
@@ -1113,8 +1114,10 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
const parsedAnchor = JSON.parse(anchor);
|
||||
if (!Array.isArray(parsedAnchor)) {
|
||||
// only show filters scoped to anchor
|
||||
const anchorFilters: NativeFilterObject[] =
|
||||
nativeFilters?.[anchor] ?? [];
|
||||
setNativeFilterOptions(
|
||||
nativeFilters[anchor].map((filter: any) => ({
|
||||
anchorFilters.map(filter => ({
|
||||
value: filter.id,
|
||||
label: filter.name,
|
||||
})),
|
||||
@@ -1122,7 +1125,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
}
|
||||
if (Array.isArray(parsedAnchor)) {
|
||||
// Check if all elements in parsedAnchor list are in allTabs
|
||||
const isValidSubset = parsedAnchor.every(tab => tab in allTabs);
|
||||
const isValidSubset =
|
||||
allTabs && parsedAnchor.every(tab => tab in allTabs);
|
||||
if (!isValidSubset) {
|
||||
updateAnchorState(undefined);
|
||||
}
|
||||
@@ -1130,13 +1134,13 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
throw new Error('Parsed value is not an array');
|
||||
}
|
||||
} catch (error) {
|
||||
if (!(anchor in allTabs)) {
|
||||
if (!allTabs || !(anchor in allTabs)) {
|
||||
updateAnchorState(undefined);
|
||||
}
|
||||
}
|
||||
} else if (nativeFilters.all) {
|
||||
} else if (nativeFilters?.all) {
|
||||
setNativeFilterOptions(
|
||||
nativeFilters.all.map((filter: any) => ({
|
||||
nativeFilters.all.map(filter => ({
|
||||
value: filter.id,
|
||||
label: filter.name,
|
||||
})),
|
||||
@@ -1438,8 +1442,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||
return;
|
||||
|
||||
// find specific filter tied to the selected filter
|
||||
const filters = Object.values(tabNativeFilters).flat();
|
||||
const filter = filters.filter((f: any) => f.id === nativeFilterId)[0];
|
||||
const filters = Object.values(tabNativeFilters).flatMap(arr => arr ?? []);
|
||||
const filter = filters.filter(f => f.id === nativeFilterId)[0];
|
||||
|
||||
const { filterType, adhoc_filters: adhocFilters } = filter;
|
||||
const filterAlreadyExist = nativeFilterData.some(
|
||||
|
||||
@@ -231,6 +231,7 @@ export type NativeFilterObject = {
|
||||
rootPath: string[];
|
||||
};
|
||||
tabsInScope: string[];
|
||||
adhoc_filters: any[];
|
||||
targets: Array<{
|
||||
column: {
|
||||
name: string;
|
||||
@@ -239,3 +240,9 @@ export type NativeFilterObject = {
|
||||
}>;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type DashboardTabsResponse = {
|
||||
tab_tree: TabNode[];
|
||||
all_tabs: Record<string, string>;
|
||||
native_filters: Partial<Record<string, NativeFilterObject[]>>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user