mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.18`: - [[Obs AI Assistant] Make KB retrieval namespace specific (#213505)](https://github.com/elastic/kibana/pull/213505) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Viduni Wickramarachchi","email":"viduni.wickramarachchi@elastic.co"},"sourceCommit":{"committedDate":"2025-03-11T17:44:28Z","message":"[Obs AI Assistant] Make KB retrieval namespace specific (#213505)\n\nCloses https://github.com/elastic/kibana/issues/213504\n\n## Summary\n\n### Problem\n\nKB retrievals are not space specific at present. Therefore, users are\nable to view entries across spaces.\n\n### Solution\n\nFilter by `namespace` when retrieving KB entries.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"9b1455c7f7beeddd70d2ecefaa58bd6f5ff8cb0e","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","Team:Obs AI Assistant","ci:project-deploy-observability","backport:version","v8.18.0","v9.1.0","v8.19.0","v8.17.4"],"title":"[Obs AI Assistant] Make KB retrieval namespace specific ","number":213505,"url":"https://github.com/elastic/kibana/pull/213505","mergeCommit":{"message":"[Obs AI Assistant] Make KB retrieval namespace specific (#213505)\n\nCloses https://github.com/elastic/kibana/issues/213504\n\n## Summary\n\n### Problem\n\nKB retrievals are not space specific at present. Therefore, users are\nable to view entries across spaces.\n\n### Solution\n\nFilter by `namespace` when retrieving KB entries.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"9b1455c7f7beeddd70d2ecefaa58bd6f5ff8cb0e"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x","8.17"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213505","number":213505,"mergeCommit":{"message":"[Obs AI Assistant] Make KB retrieval namespace specific (#213505)\n\nCloses https://github.com/elastic/kibana/issues/213504\n\n## Summary\n\n### Problem\n\nKB retrievals are not space specific at present. Therefore, users are\nable to view entries across spaces.\n\n### Solution\n\nFilter by `namespace` when retrieving KB entries.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"9b1455c7f7beeddd70d2ecefaa58bd6f5ff8cb0e"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Viduni Wickramarachchi <viduni.wickramarachchi@elastic.co>
This commit is contained in:
parent
d7ff506e03
commit
f8e55ed735
4 changed files with 173 additions and 18 deletions
|
@ -771,7 +771,12 @@ export class ObservabilityAIAssistantClient {
|
|||
sortBy: string;
|
||||
sortDirection: 'asc' | 'desc';
|
||||
}) => {
|
||||
return this.dependencies.knowledgeBaseService.getEntries({ query, sortBy, sortDirection });
|
||||
return this.dependencies.knowledgeBaseService.getEntries({
|
||||
query,
|
||||
sortBy,
|
||||
sortDirection,
|
||||
namespace: this.dependencies.namespace,
|
||||
});
|
||||
};
|
||||
|
||||
deleteKnowledgeBaseEntry = async (id: string) => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '../../../common/types';
|
||||
import { getAccessQuery, getUserAccessFilters } from '../util/get_access_query';
|
||||
import { getCategoryQuery } from '../util/get_category_query';
|
||||
import { getSpaceQuery } from '../util/get_space_query';
|
||||
import {
|
||||
createInferenceEndpoint,
|
||||
deleteInferenceEndpoint,
|
||||
|
@ -259,14 +260,17 @@ export class KnowledgeBaseService {
|
|||
query,
|
||||
sortBy,
|
||||
sortDirection,
|
||||
namespace,
|
||||
}: {
|
||||
query?: string;
|
||||
sortBy?: string;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
namespace: string;
|
||||
}): Promise<{ entries: KnowledgeBaseEntry[] }> => {
|
||||
if (!this.dependencies.config.enableKnowledgeBase) {
|
||||
return { entries: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.dependencies.esClient.asInternalUser.search<
|
||||
KnowledgeBaseEntry & { doc_id?: string }
|
||||
|
@ -281,8 +285,12 @@ export class KnowledgeBaseService {
|
|||
: []),
|
||||
{
|
||||
// exclude user instructions
|
||||
bool: { must_not: { term: { type: KnowledgeBaseType.UserInstruction } } },
|
||||
bool: {
|
||||
must_not: { term: { type: KnowledgeBaseType.UserInstruction } },
|
||||
},
|
||||
},
|
||||
// filter by space
|
||||
...getSpaceQuery({ namespace }),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -425,6 +433,7 @@ export class KnowledgeBaseService {
|
|||
},
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
|
||||
this.dependencies.logger.debug(`Entry added to knowledge base`);
|
||||
} catch (error) {
|
||||
this.dependencies.logger.debug(`Failed to add entry to knowledge base ${error}`);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export function getSpaceQuery({ namespace }: { namespace?: string }) {
|
||||
return [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{ term: { namespace } },
|
||||
{ bool: { must_not: { exists: { field: 'namespace' } } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
|
@ -24,6 +24,31 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
const retry = getService('retry');
|
||||
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantApi');
|
||||
|
||||
async function getEntries({
|
||||
query = '',
|
||||
sortBy = 'title',
|
||||
sortDirection = 'asc',
|
||||
spaceId,
|
||||
user = 'editor',
|
||||
}: {
|
||||
query?: string;
|
||||
sortBy?: string;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
spaceId?: string;
|
||||
user?: 'admin' | 'editor' | 'viewer';
|
||||
} = {}): Promise<KnowledgeBaseEntry[]> {
|
||||
const res = await observabilityAIAssistantAPIClient[user]({
|
||||
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
|
||||
params: {
|
||||
query: { query, sortBy, sortDirection },
|
||||
},
|
||||
spaceId,
|
||||
});
|
||||
expect(res.status).to.be(200);
|
||||
|
||||
return omitCategories(res.body.entries);
|
||||
}
|
||||
|
||||
describe('Knowledge base', function () {
|
||||
before(async () => {
|
||||
await importTinyElserModel(ml);
|
||||
|
@ -124,22 +149,6 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
});
|
||||
|
||||
describe('when managing multiple entries', () => {
|
||||
async function getEntries({
|
||||
query = '',
|
||||
sortBy = 'title',
|
||||
sortDirection = 'asc',
|
||||
}: { query?: string; sortBy?: string; sortDirection?: 'asc' | 'desc' } = {}) {
|
||||
const res = await observabilityAIAssistantAPIClient.editor({
|
||||
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
|
||||
params: {
|
||||
query: { query, sortBy, sortDirection },
|
||||
},
|
||||
});
|
||||
expect(res.status).to.be(200);
|
||||
|
||||
return omitCategories(res.body.entries);
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearKnowledgeBase(es);
|
||||
|
||||
|
@ -200,6 +209,119 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
});
|
||||
});
|
||||
|
||||
describe('when managing multiple entries across spaces', () => {
|
||||
const SPACE_A_ID = 'space_a';
|
||||
const SPACE_B_ID = 'space_b';
|
||||
|
||||
before(async () => {
|
||||
await clearKnowledgeBase(es);
|
||||
|
||||
const { status: importStatusForSpaceA } = await observabilityAIAssistantAPIClient.admin({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
|
||||
params: {
|
||||
body: {
|
||||
entries: [
|
||||
{
|
||||
id: 'my-doc-1',
|
||||
title: `Entry in Space A by Admin 1`,
|
||||
text: `This is a public entry in Space A created by Admin`,
|
||||
public: true,
|
||||
},
|
||||
{
|
||||
id: 'my-doc-2',
|
||||
title: `Entry in Space A by Admin 2`,
|
||||
text: `This is a private entry in Space A created by Admin`,
|
||||
public: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
spaceId: SPACE_A_ID,
|
||||
});
|
||||
expect(importStatusForSpaceA).to.be(200);
|
||||
|
||||
const { status: importStatusForSpaceB } = await observabilityAIAssistantAPIClient.admin({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
|
||||
params: {
|
||||
body: {
|
||||
entries: [
|
||||
{
|
||||
id: 'my-doc-3',
|
||||
title: `Entry in Space B by Admin 3`,
|
||||
text: `This is a public entry in Space B created by Admin`,
|
||||
public: true,
|
||||
},
|
||||
{
|
||||
id: 'my-doc-4',
|
||||
title: `Entry in Space B by Admin 4`,
|
||||
text: `This is a private entry in Space B created by Admin`,
|
||||
public: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
spaceId: SPACE_B_ID,
|
||||
});
|
||||
expect(importStatusForSpaceB).to.be(200);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await clearKnowledgeBase(es);
|
||||
});
|
||||
|
||||
it('ensures users can only access entries relevant to their namespace', async () => {
|
||||
// User (admin) in space A should only see entries in space A
|
||||
const spaceAEntries = await getEntries({
|
||||
user: 'admin',
|
||||
spaceId: SPACE_A_ID,
|
||||
});
|
||||
|
||||
expect(spaceAEntries.length).to.be(2);
|
||||
|
||||
expect(spaceAEntries[0].id).to.equal('my-doc-1');
|
||||
expect(spaceAEntries[0].public).to.be(true);
|
||||
expect(spaceAEntries[0].title).to.equal('Entry in Space A by Admin 1');
|
||||
|
||||
expect(spaceAEntries[1].id).to.equal('my-doc-2');
|
||||
expect(spaceAEntries[1].public).to.be(false);
|
||||
expect(spaceAEntries[1].title).to.equal('Entry in Space A by Admin 2');
|
||||
|
||||
// User (admin) in space B should only see entries in space B
|
||||
const spaceBEntries = await getEntries({
|
||||
user: 'admin',
|
||||
spaceId: SPACE_B_ID,
|
||||
});
|
||||
|
||||
expect(spaceBEntries.length).to.be(2);
|
||||
|
||||
expect(spaceBEntries[0].id).to.equal('my-doc-3');
|
||||
expect(spaceBEntries[0].public).to.be(true);
|
||||
expect(spaceBEntries[0].title).to.equal('Entry in Space B by Admin 3');
|
||||
|
||||
expect(spaceBEntries[1].id).to.equal('my-doc-4');
|
||||
expect(spaceBEntries[1].public).to.be(false);
|
||||
expect(spaceBEntries[1].title).to.equal('Entry in Space B by Admin 4');
|
||||
});
|
||||
|
||||
it('should allow a user who is not the owner of the entries to access entries relevant to their namespace', async () => {
|
||||
// User (editor) in space B should only see entries in space B
|
||||
const spaceBEntries = await getEntries({
|
||||
user: 'editor',
|
||||
spaceId: SPACE_B_ID,
|
||||
});
|
||||
|
||||
expect(spaceBEntries.length).to.be(2);
|
||||
|
||||
expect(spaceBEntries[0].id).to.equal('my-doc-3');
|
||||
expect(spaceBEntries[0].public).to.be(true);
|
||||
expect(spaceBEntries[0].title).to.equal('Entry in Space B by Admin 3');
|
||||
|
||||
expect(spaceBEntries[1].id).to.equal('my-doc-4');
|
||||
expect(spaceBEntries[1].public).to.be(false);
|
||||
expect(spaceBEntries[1].title).to.equal('Entry in Space B by Admin 4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('security roles and access privileges', () => {
|
||||
describe('should deny access for users without the ai_assistant privilege', () => {
|
||||
it('POST /internal/observability_ai_assistant/kb/entries/save', async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue