diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index d40b91188cfc..1e08ba9593cc 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -390,6 +390,7 @@ export class SavedObjectsClient { namespaces: 'namespaces', preference: 'preference', workspaces: 'workspaces', + enabledOperators: 'enabledOperators', }; const renamedQuery = renameKeys(renameMap, { diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 34ff9b1e0d8f..9e63517a0a76 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -801,6 +801,7 @@ export class SavedObjectsRepository { workspaces, workspacesSearchOperator, ACLSearchParams, + enabledOperators, } = options; if (!type && !typeToNamespacesMap) { @@ -877,6 +878,7 @@ export class SavedObjectsRepository { workspaces, workspacesSearchOperator, ACLSearchParams, + enabledOperators, }), }, }; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 5af816a1d8f5..19ae0cabfbde 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -506,6 +506,25 @@ describe('#getQueryParams', () => { ); }); }); + + describe('`enabledOperators` parameter', () => { + it('does not include flags when `enabledOperators` is not specified', () => { + const result = getQueryParams({ + registry, + search, + }); + expectResult(result, expect.not.objectContaining({ flags: expect.anything() })); + }); + + it('includes flags when `enabledOperators` specified', () => { + const result = getQueryParams({ + registry, + search, + enabledOperators: 'all', + }); + expectResult(result, expect.objectContaining({ flags: expect.stringMatching('all') })); + }); + }); }); describe('when using prefix search (query.bool.should)', () => { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 318768fd83c2..1016a8dfefa4 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -170,6 +170,7 @@ interface QueryParams { workspaces?: SavedObjectsFindOptions['workspaces']; workspacesSearchOperator?: 'AND' | 'OR'; ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams']; + enabledOperators?: SavedObjectsFindOptions['enabledOperators']; } export function getClauseForReference(reference: HasReferenceQueryParams) { @@ -229,6 +230,7 @@ export function getQueryParams({ workspaces, workspacesSearchOperator = 'AND', ACLSearchParams, + enabledOperators, }: QueryParams) { const types = getTypes( registry, @@ -261,6 +263,7 @@ export function getQueryParams({ searchFields, rootSearchFields, defaultSearchOperator, + enabledOperators, }); if (useMatchPhrasePrefix) { @@ -432,18 +435,21 @@ const getSimpleQueryStringClause = ({ searchFields, rootSearchFields, defaultSearchOperator, + enabledOperators, }: { search: string; types: string[]; searchFields?: string[]; rootSearchFields?: string[]; defaultSearchOperator?: string; + enabledOperators?: SavedObjectsFindOptions['enabledOperators']; }) => { return { simple_query_string: { query: search, ...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields), ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), + ...(enabledOperators ? { flags: enabledOperators } : {}), }, }; }; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 626fc7efba3e..86a4ba02882a 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -56,6 +56,7 @@ interface GetSearchDslOptions { workspaces?: SavedObjectsFindOptions['workspaces']; workspacesSearchOperator?: 'AND' | 'OR'; ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams']; + enabledOperators?: SavedObjectsFindOptions['enabledOperators']; } export function getSearchDsl( @@ -78,6 +79,7 @@ export function getSearchDsl( workspaces, workspacesSearchOperator, ACLSearchParams, + enabledOperators, } = options; if (!type) { @@ -103,6 +105,7 @@ export function getSearchDsl( workspaces, workspacesSearchOperator, ACLSearchParams, + enabledOperators, }), ...getSortingParams(mappings, type, sortField, sortOrder), }; diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 369cbfd53bf4..b4c9e25eb678 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -92,6 +92,8 @@ export interface SavedObjectsFindOptions { search?: string; /** The fields to perform the parsed query against. See OpenSearch Simple Query String `fields` argument for more information */ searchFields?: string[]; + /** The enabled operators for OpenSearch Simple Query String. See OpenSearch Simple Query String `flags` argument for more information */ + enabledOperators?: string; /** * The fields to perform the parsed query against. Unlike the `searchFields` argument, these are expected to be root fields and will not * be modified. If used in conjunction with `searchFields`, both are concatenated together. diff --git a/src/plugins/workspace/server/integration_tests/routes.test.ts b/src/plugins/workspace/server/integration_tests/routes.test.ts index 972a43a17389..175319074d10 100644 --- a/src/plugins/workspace/server/integration_tests/routes.test.ts +++ b/src/plugins/workspace/server/integration_tests/routes.test.ts @@ -88,6 +88,42 @@ describe('workspace service api integration test', () => { expect(result.body.success).toEqual(true); expect(typeof result.body.result.id).toBe('string'); }); + it('create workspace failed when name duplicate', async () => { + let result: any = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: omitId(testWorkspace), + }) + .expect(200); + + expect(result.body.success).toEqual(true); + + await opensearchServer.opensearch.getClient().indices.refresh({ index: '.kibana' }); + + // same name + result = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: omitId(testWorkspace), + }) + .expect(200); + + expect(result.body.success).toEqual(false); + expect(result.body.error).toEqual( + 'workspace name has already been used, try with a different name' + ); + + // verify simple query string flags is NONE + result = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: { ...omitId(testWorkspace), name: 'test test_workspace' }, + }) + .expect(200); + + expect(result.body.success).toEqual(true); + }); + it('get', async () => { const result = await osdTestServer.request .post(root, `/api/workspaces`) @@ -128,6 +164,56 @@ describe('workspace service api integration test', () => { expect(getResult.body.success).toEqual(true); expect(getResult.body.result.name).toEqual('updated'); }); + + it('update workspace failed when name is duplicate', async () => { + const result: any = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: { ...omitId(testWorkspace), name: 'foo' }, + }) + .expect(200); + + await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: { ...omitId(testWorkspace), name: 'bar baz' }, + }) + .expect(200); + + const updateResult = await osdTestServer.request + .put(root, `/api/workspaces/${result.body.result.id}`) + .send({ + attributes: { + ...omitId(testWorkspace), + name: 'bar baz', + }, + }) + .expect(200); + + expect(updateResult.body.success).toEqual(false); + expect(updateResult.body.error).toEqual( + 'workspace name has already been used, try with a different name' + ); + + await osdTestServer.request + .put(root, `/api/workspaces/${result.body.result.id}`) + .send({ + attributes: { + ...omitId(testWorkspace), + name: 'bar', + }, + }) + .expect(200); + + const getResult = await osdTestServer.request.get( + root, + `/api/workspaces/${result.body.result.id}` + ); + + expect(getResult.body.success).toEqual(true); + expect(getResult.body.result.name).toEqual('bar'); + }); + it('delete', async () => { const result: any = await osdTestServer.request .post(root, `/api/workspaces`) diff --git a/src/plugins/workspace/server/workspace_client.ts b/src/plugins/workspace/server/workspace_client.ts index 9462bfdd9242..b4c8f2ebd637 100644 --- a/src/plugins/workspace/server/workspace_client.ts +++ b/src/plugins/workspace/server/workspace_client.ts @@ -134,6 +134,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl { type: WORKSPACE_TYPE, search: attributes.name, searchFields: ['name'], + enabledOperators: 'NONE', // disable all operators, treat workspace as literal string } ); if (existingWorkspaceRes && existingWorkspaceRes.total > 0) { @@ -260,6 +261,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl { search: attributes.name, searchFields: ['name'], fields: ['_id'], + enabledOperators: 'NONE', // disable all operators, treat workspace as literal string }); if (existingWorkspaceRes && existingWorkspaceRes.total > 0) { throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR);