Skip to content

Commit 9b5768e

Browse files
authored
Merge branch 'main' into docs/data-access-v3-architecture
2 parents baa5fa4 + 298c6e7 commit 9b5768e

File tree

6 files changed

+151
-3
lines changed

6 files changed

+151
-3
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/spacecat-shared-splunk-client/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [@adobe/spacecat-shared-splunk-client-v1.1.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-splunk-client-v1.0.30...@adobe/spacecat-shared-splunk-client-v1.1.0) (2026-02-27)
2+
3+
### Features
4+
5+
* add slack for redirects ([#1365](https://github.com/adobe/spacecat-shared/issues/1365)) ([7b8176e](https://github.com/adobe/spacecat-shared/commit/7b8176ec7e6788b60da7b76f8924c71f4ef0b26a))
6+
17
# [@adobe/spacecat-shared-splunk-client-v1.0.30](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-splunk-client-v1.0.29...@adobe/spacecat-shared-splunk-client-v1.0.30) (2026-01-29)
28

39

packages/spacecat-shared-splunk-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adobe/spacecat-shared-splunk-client",
3-
"version": "1.0.30",
3+
"version": "1.1.0",
44
"description": "Shared modules of the Spacecat Services - Splunk Client",
55
"type": "module",
66
"engines": {

packages/spacecat-shared-splunk-client/src/index.d.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export interface SplunkAPIOptions {
2222
mode: string;
2323
output: string;
2424
}
25+
export type SplunkLoginResult =
26+
| { sessionId: string; cookie: string }
27+
| { error: unknown };
28+
29+
export interface SplunkOneshotResponse {
30+
results?: Array<Record<string, unknown>>;
31+
[key: string]: unknown;
32+
}
2533

2634
export default class SplunkAPIClient {
2735
/**
@@ -45,7 +53,13 @@ export default class SplunkAPIClient {
4553
* @param password
4654
*/
4755
login(username?: string, password?: string):
48-
Promise<{ result: object }>;
56+
Promise<SplunkLoginResult>;
57+
58+
/**
59+
* Execute an ad-hoc Splunk oneshot search.
60+
* Throws on error.
61+
*/
62+
oneshotSearch(searchString: string): Promise<SplunkOneshotResponse>;
4963

5064
/**
5165
* Asynchronous method to check for Not Found errors
@@ -56,5 +70,8 @@ export default class SplunkAPIClient {
5670
* @param password
5771
*/
5872
getNotFounds(minutes?: number, username?: string, password?: string):
59-
Promise<{ result: object }>;
73+
Promise<
74+
| { results: Array<Record<string, unknown>> }
75+
| { error: unknown }
76+
>;
6077
}

packages/spacecat-shared-splunk-client/src/index.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,51 @@ export default class SplunkAPIClient {
144144

145145
return returnObj;
146146
}
147+
148+
/**
149+
* Execute an ad-hoc Splunk oneshot search and return the JSON payload.
150+
*
151+
* @param {string} searchString SPL query string (e.g. `search index=... | stats ...`)
152+
* @returns {Promise<object>} Splunk oneshot search response JSON
153+
*/
154+
async oneshotSearch(searchString) {
155+
if (typeof searchString !== 'string' || searchString.trim().length === 0) {
156+
throw new Error('Missing searchString');
157+
}
158+
159+
// Additive behavior: reuse existing login state, otherwise login now.
160+
if (!this.loginObj) {
161+
this.loginObj = await this.login();
162+
}
163+
if (this.loginObj?.error) {
164+
const err = this.loginObj.error;
165+
this.loginObj = null;
166+
throw (err instanceof Error ? err : new Error(String(err)));
167+
}
168+
169+
const queryBody = new URLSearchParams({
170+
search: searchString,
171+
adhoc_search_level: 'fast',
172+
exec_mode: 'oneshot',
173+
output_mode: 'json',
174+
});
175+
176+
const url = `${this.apiBaseUrl}/servicesNS/admin/search/search/jobs`;
177+
const response = await this.fetchAPI(url, {
178+
method: 'POST',
179+
headers: {
180+
'Content-Type': 'application/json',
181+
Authorization: `Splunk ${this.loginObj.sessionId}`,
182+
Cookie: this.loginObj.cookie,
183+
},
184+
body: queryBody,
185+
});
186+
187+
if (response.status !== 200) {
188+
const body = await response.text();
189+
throw new Error(`Splunk oneshot search failed. Status: ${response.status}. Body: ${body}`);
190+
}
191+
192+
return response.json();
193+
}
147194
}

packages/spacecat-shared-splunk-client/test/index.test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,80 @@ describe('SplunkAPIClient unit tests', () => {
210210
});
211211
});
212212
});
213+
214+
describe('oneshotSearch', () => {
215+
it('throws when searchString is missing', async () => {
216+
await expect(client.oneshotSearch('')).to.be.rejectedWith('Missing searchString');
217+
});
218+
219+
it('runs oneshot search after login', async () => {
220+
nock(config.apiBaseUrl)
221+
.post('/services/auth/login')
222+
.reply(200, loginSuccessResponse, { 'Set-Cookie': loginSuccessSetCookieHeader });
223+
224+
nock(config.apiBaseUrl)
225+
.post('/servicesNS/admin/search/search/jobs')
226+
.reply(200, notFoundsResponse);
227+
228+
const resp = await client.oneshotSearch('search index=dx_aem_engineering | head 1');
229+
expect(resp).to.deep.equal(notFoundsResponse);
230+
});
231+
232+
it('reuses login across calls', async () => {
233+
nock(config.apiBaseUrl)
234+
.post('/services/auth/login')
235+
.once()
236+
.reply(200, loginSuccessResponse, { 'Set-Cookie': loginSuccessSetCookieHeader });
237+
238+
nock(config.apiBaseUrl)
239+
.post('/servicesNS/admin/search/search/jobs')
240+
.twice()
241+
.reply(200, notFoundsResponse);
242+
243+
await client.oneshotSearch('search index=dx_aem_engineering | head 1');
244+
await client.oneshotSearch('search index=dx_aem_engineering | head 1');
245+
});
246+
247+
it('runs without logging in if loginObj is already set', async () => {
248+
client.loginObj = { sessionId: 'sessionKey123', cookie: 'cookie123' };
249+
250+
nock(config.apiBaseUrl)
251+
.post('/servicesNS/admin/search/search/jobs')
252+
.reply(200, notFoundsResponse);
253+
254+
const resp = await client.oneshotSearch('search index=dx_aem_engineering | head 1');
255+
expect(resp).to.deep.equal(notFoundsResponse);
256+
});
257+
258+
it('throws with non-200 response', async () => {
259+
nock(config.apiBaseUrl)
260+
.post('/services/auth/login')
261+
.reply(200, loginSuccessResponse, { 'Set-Cookie': loginSuccessSetCookieHeader });
262+
263+
nock(config.apiBaseUrl)
264+
.post('/servicesNS/admin/search/search/jobs')
265+
.reply(400, { error: 'bad_request' });
266+
267+
await expect(client.oneshotSearch('search index=dx_aem_engineering | head 1'))
268+
.to.be.rejectedWith('Splunk oneshot search failed. Status: 400');
269+
});
270+
271+
it('propagates login parse errors (error is an Error instance)', async () => {
272+
nock(config.apiBaseUrl)
273+
.post('/services/auth/login')
274+
.reply(200, invalidResponse);
275+
276+
await expect(client.oneshotSearch('search index=dx_aem_engineering | head 1'))
277+
.to.be.rejected;
278+
});
279+
280+
it('throws when login fails (error is string)', async () => {
281+
nock(config.apiBaseUrl)
282+
.post('/services/auth/login')
283+
.reply(401, loginFailedResponse);
284+
285+
await expect(client.oneshotSearch('search index=dx_aem_engineering | head 1'))
286+
.to.be.rejectedWith('Login failed');
287+
});
288+
});
213289
});

0 commit comments

Comments
 (0)