Skip to content

Commit 3839361

Browse files
lunalzmopensearch-changeset-bot[bot]SuZhou-Joe
authored
Add validation for deleted data source in default index pattern (#9703)
* add-deleted-data-source-logic Signed-off-by: Luna Liu <[email protected]> * Changeset file for PR #9703 created/updated * default-index-pattern-test Signed-off-by: Luna Liu <[email protected]> * default-index-pattern-test Signed-off-by: Luna Liu <[email protected]> * default-index-pattern-update Signed-off-by: Luna Liu <[email protected]> * default-index-pattern-update Signed-off-by: Luna Liu <[email protected]> * default-index-pattern-update Signed-off-by: Luna Liu <[email protected]> * update-logic Signed-off-by: Luna Liu <[email protected]> * update-test Signed-off-by: Luna Liu <[email protected]> * update-test Signed-off-by: Luna Liu <[email protected]> * update-logic-and-test Signed-off-by: Luna Liu <[email protected]> * update-logic-and-test Signed-off-by: Luna Liu <[email protected]> * update-logic Signed-off-by: Luna Liu <[email protected]> --------- Signed-off-by: Luna Liu <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Co-authored-by: SuZhou-Joe <[email protected]>
1 parent 69a8695 commit 3839361

File tree

4 files changed

+295
-5
lines changed

4 files changed

+295
-5
lines changed

changelogs/fragments/9703.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
feat:
2+
- Update default index pattern logic ([#9703](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/9703))
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { createEnsureDefaultIndexPattern } from './ensure_default_index_pattern';
7+
import { IndexPatternsContract } from './index_patterns';
8+
import { UiSettingsCommon, SavedObjectsClientCommon } from '../types';
9+
10+
describe('ensureDefaultIndexPattern', () => {
11+
let uiSettings: UiSettingsCommon;
12+
let onRedirectNoIndexPattern: jest.Mock;
13+
let savedObjectsClient: SavedObjectsClientCommon;
14+
let indexPatterns: IndexPatternsContract;
15+
let ensureDefaultIndexPattern: () => Promise<unknown | void> | undefined;
16+
17+
beforeEach(() => {
18+
uiSettings = ({
19+
get: jest.fn(),
20+
set: jest.fn(),
21+
remove: jest.fn(),
22+
} as unknown) as UiSettingsCommon;
23+
24+
onRedirectNoIndexPattern = jest.fn();
25+
savedObjectsClient = ({
26+
find: jest.fn(),
27+
} as unknown) as SavedObjectsClientCommon;
28+
29+
indexPatterns = ({
30+
getIds: jest.fn(),
31+
get: jest.fn(),
32+
getDataSource: jest.fn(),
33+
} as unknown) as IndexPatternsContract;
34+
});
35+
36+
test('should return early if canUpdateUiSetting is false', async () => {
37+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
38+
uiSettings,
39+
onRedirectNoIndexPattern,
40+
false,
41+
savedObjectsClient
42+
);
43+
44+
await ensureDefaultIndexPattern.call(indexPatterns);
45+
expect(uiSettings.get).not.toHaveBeenCalled();
46+
expect(indexPatterns.getIds).not.toHaveBeenCalled();
47+
});
48+
49+
test('should set first available pattern as default when no valid default exists', async () => {
50+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
51+
uiSettings,
52+
onRedirectNoIndexPattern,
53+
true,
54+
savedObjectsClient
55+
);
56+
57+
(indexPatterns.getIds as jest.Mock).mockResolvedValue(['pattern1']);
58+
(uiSettings.get as jest.Mock).mockImplementation((key) => {
59+
if (key === 'defaultIndex') return 'invalid-pattern';
60+
return false;
61+
});
62+
(indexPatterns.get as jest.Mock).mockResolvedValue({
63+
dataSourceRef: { id: 'ds1' },
64+
});
65+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
66+
error: { statusCode: 404 },
67+
});
68+
(savedObjectsClient.find as jest.Mock).mockImplementation((params) => {
69+
if (params.type === 'data-source') {
70+
return Promise.resolve([{ id: 'ds1' }]);
71+
}
72+
if (params.type === 'index-pattern') {
73+
return Promise.resolve([
74+
{
75+
id: 'pattern1',
76+
references: [{ id: 'ds1' }],
77+
},
78+
]);
79+
}
80+
});
81+
82+
await ensureDefaultIndexPattern.call(indexPatterns);
83+
expect(indexPatterns.getIds).toHaveBeenCalled();
84+
expect(uiSettings.set).toHaveBeenCalledWith('defaultIndex', 'pattern1');
85+
});
86+
87+
test('should redirect when no patterns available and enhancements disabled', async () => {
88+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
89+
uiSettings,
90+
onRedirectNoIndexPattern,
91+
true,
92+
savedObjectsClient
93+
);
94+
95+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([]);
96+
(uiSettings.get as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(false);
97+
(savedObjectsClient.find as jest.Mock).mockResolvedValueOnce([]).mockResolvedValueOnce([]);
98+
99+
await ensureDefaultIndexPattern.call(indexPatterns);
100+
expect(onRedirectNoIndexPattern).toHaveBeenCalled();
101+
});
102+
103+
test('should handle invalid data source', async () => {
104+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
105+
uiSettings,
106+
onRedirectNoIndexPattern,
107+
true,
108+
savedObjectsClient
109+
);
110+
111+
const defaultId = 'pattern1';
112+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([defaultId]);
113+
(uiSettings.get as jest.Mock).mockResolvedValue(defaultId);
114+
(indexPatterns.get as jest.Mock).mockResolvedValue({
115+
dataSourceRef: { id: 'ds1' },
116+
});
117+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
118+
error: { statusCode: 404 },
119+
});
120+
121+
await ensureDefaultIndexPattern.call(indexPatterns);
122+
expect(savedObjectsClient.find).toHaveBeenCalledWith({ type: 'data-source' });
123+
});
124+
125+
test('should return early if index pattern has no dataSourceRef', async () => {
126+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
127+
uiSettings,
128+
onRedirectNoIndexPattern,
129+
true,
130+
savedObjectsClient
131+
);
132+
133+
const defaultId = 'pattern1';
134+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([defaultId]);
135+
(uiSettings.get as jest.Mock).mockResolvedValue(defaultId);
136+
(indexPatterns.get as jest.Mock).mockResolvedValue({});
137+
138+
await ensureDefaultIndexPattern.call(indexPatterns);
139+
expect(indexPatterns.getDataSource).not.toHaveBeenCalled();
140+
});
141+
142+
test('should handle successful data source lookup', async () => {
143+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
144+
uiSettings,
145+
onRedirectNoIndexPattern,
146+
true,
147+
savedObjectsClient
148+
);
149+
150+
const defaultId = 'pattern1';
151+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([defaultId]);
152+
(uiSettings.get as jest.Mock).mockResolvedValue(defaultId);
153+
(indexPatterns.get as jest.Mock).mockResolvedValue({
154+
dataSourceRef: { id: 'ds1' },
155+
});
156+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
157+
data: {},
158+
});
159+
160+
await ensureDefaultIndexPattern.call(indexPatterns);
161+
expect(savedObjectsClient.find).not.toHaveBeenCalled();
162+
});
163+
164+
test('should process available patterns when data source lookup fails', async () => {
165+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
166+
uiSettings,
167+
onRedirectNoIndexPattern,
168+
true,
169+
savedObjectsClient
170+
);
171+
172+
const defaultId = 'pattern1';
173+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([defaultId]);
174+
(uiSettings.get as jest.Mock).mockResolvedValue(defaultId);
175+
(indexPatterns.get as jest.Mock).mockResolvedValue({
176+
dataSourceRef: { id: 'ds1' },
177+
});
178+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
179+
error: { statusCode: 403 },
180+
});
181+
(savedObjectsClient.find as jest.Mock).mockImplementation((params) => {
182+
if (params.type === 'data-source') {
183+
return Promise.resolve([{ id: 'ds1' }]);
184+
}
185+
if (params.type === 'index-pattern') {
186+
return Promise.resolve([
187+
{
188+
id: 'pattern1',
189+
references: [{ id: 'ds1' }],
190+
},
191+
]);
192+
}
193+
});
194+
195+
await ensureDefaultIndexPattern.call(indexPatterns);
196+
expect(uiSettings.set).toHaveBeenCalledWith('defaultIndex', 'pattern1');
197+
});
198+
199+
test('should handle error in savedObjectsClient.find', async () => {
200+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
201+
uiSettings,
202+
onRedirectNoIndexPattern,
203+
true,
204+
savedObjectsClient
205+
);
206+
207+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([]);
208+
(uiSettings.get as jest.Mock).mockResolvedValue(null);
209+
(indexPatterns.get as jest.Mock).mockResolvedValue({
210+
dataSourceRef: { id: 'ds1' },
211+
});
212+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
213+
error: { statusCode: 403 },
214+
});
215+
(savedObjectsClient.find as jest.Mock).mockRejectedValue(new Error('Failed to find'));
216+
217+
await ensureDefaultIndexPattern.call(indexPatterns);
218+
expect(onRedirectNoIndexPattern).toHaveBeenCalled();
219+
});
220+
221+
test('should handle index patterns with invalid references', async () => {
222+
ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
223+
uiSettings,
224+
onRedirectNoIndexPattern,
225+
true,
226+
savedObjectsClient
227+
);
228+
229+
const defaultId = 'pattern1';
230+
(indexPatterns.getIds as jest.Mock).mockResolvedValue([defaultId]);
231+
(uiSettings.get as jest.Mock).mockResolvedValue(defaultId);
232+
(indexPatterns.get as jest.Mock).mockResolvedValue({
233+
dataSourceRef: { id: 'ds1' },
234+
});
235+
(indexPatterns.getDataSource as jest.Mock).mockResolvedValue({
236+
error: { statusCode: 403 },
237+
});
238+
(savedObjectsClient.find as jest.Mock).mockImplementation((params) => {
239+
if (params.type === 'data-source') {
240+
return Promise.resolve([{ id: 'ds2' }]);
241+
}
242+
if (params.type === 'index-pattern') {
243+
return Promise.resolve([
244+
{
245+
id: 'pattern1',
246+
references: [],
247+
},
248+
{
249+
id: 'pattern2',
250+
references: [{ id: 'ds3' }],
251+
},
252+
]);
253+
}
254+
});
255+
256+
await ensureDefaultIndexPattern.call(indexPatterns);
257+
expect(uiSettings.set).toHaveBeenCalledWith('defaultIndex', 'pattern1');
258+
});
259+
});

src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@
3030

3131
import { includes } from 'lodash';
3232
import { IndexPatternsContract } from './index_patterns';
33-
import { UiSettingsCommon } from '../types';
33+
import { SavedObjectsClientCommon, UiSettingsCommon } from '../types';
3434

3535
export type EnsureDefaultIndexPattern = () => Promise<unknown | void> | undefined;
3636

3737
export const createEnsureDefaultIndexPattern = (
3838
uiSettings: UiSettingsCommon,
3939
onRedirectNoIndexPattern: () => Promise<unknown> | void,
40-
canUpdateUiSetting?: boolean
40+
canUpdateUiSetting?: boolean,
41+
savedObjectsClient?: SavedObjectsClientCommon
4142
) => {
4243
/**
4344
* Checks whether a default index pattern is set and exists and defines
@@ -47,7 +48,7 @@ export const createEnsureDefaultIndexPattern = (
4748
if (canUpdateUiSetting === false) {
4849
return;
4950
}
50-
const patterns = await this.getIds();
51+
let patterns = await this.getIds();
5152
let defaultId = await uiSettings.get('defaultIndex');
5253
let defined = !!defaultId;
5354
const exists = includes(patterns, defaultId);
@@ -58,7 +59,34 @@ export const createEnsureDefaultIndexPattern = (
5859
}
5960

6061
if (defined) {
61-
return;
62+
const indexPattern = await this.get(defaultId);
63+
const dataSourceRef = indexPattern?.dataSourceRef;
64+
if (!dataSourceRef) {
65+
return;
66+
}
67+
const result = await this.getDataSource(dataSourceRef.id);
68+
if (result.error?.statusCode === 403 || result.error?.statusCode === 404) {
69+
try {
70+
if (savedObjectsClient) {
71+
const datasources = await savedObjectsClient.find({ type: 'data-source' });
72+
const indexPatterns = await savedObjectsClient.find({ type: 'index-pattern' });
73+
const existDataSources = datasources.map((item) => item.id);
74+
patterns = [];
75+
indexPatterns.forEach((item) => {
76+
const sourceRef = item.references?.find((ref) => ref.type === 'data-source');
77+
const refId = sourceRef?.id;
78+
const refIdBool = !!refId;
79+
if (!refIdBool || existDataSources.includes(refId)) {
80+
patterns.push(item.id);
81+
}
82+
});
83+
}
84+
} catch (e) {
85+
return;
86+
}
87+
} else {
88+
return;
89+
}
6290
}
6391

6492
// If there is any index pattern created, set the first as default

src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ export class IndexPatternsService {
109109
this.ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(
110110
uiSettings,
111111
onRedirectNoIndexPattern,
112-
canUpdateUiSetting
112+
canUpdateUiSetting,
113+
savedObjectsClient
113114
);
114115
}
115116

0 commit comments

Comments
 (0)