Skip to content

Commit fa6ae1c

Browse files
committed
Add namespaceStringToId and namespaceIdToString methods to core
Now that saved objects' `namespaces` are exposed, we should provide a method to compare these strings to namespace IDs. The Spaces plugin already provided utility functions for this; I changed them to be a facade over the new core functions. The reason for this is that other plugins (alerting, actions) depend on the Spaces plugin and will use an `undefined` namespace if the Spaces plugin is not enabled.
1 parent 0c6f682 commit fa6ae1c

File tree

17 files changed

+206
-77
lines changed

17 files changed

+206
-77
lines changed

docs/development/core/server/kibana-plugin-core-server.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
219219
| Variable | Description |
220220
| --- | --- |
221221
| [kibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Set of helpers used to create <code>KibanaResponse</code> to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution. |
222+
| [namespaceIdToString](./kibana-plugin-core-server.namespaceidtostring.md) | Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the <code>undefined</code> namespace ID (which has a namespace string of <code>'default'</code>). |
223+
| [namespaceStringToId](./kibana-plugin-core-server.namespacestringtoid.md) | Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the <code>'default'</code> namespace string (which has a namespace ID of <code>undefined</code>). |
222224
| [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. |
223225
| [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output |
224226

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [namespaceIdToString](./kibana-plugin-core-server.namespaceidtostring.md)
4+
5+
## namespaceIdToString variable
6+
7+
Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the `undefined` namespace ID (which has a namespace string of `'default'`<!-- -->).
8+
9+
<b>Signature:</b>
10+
11+
```typescript
12+
namespaceIdToString: (namespace?: string | undefined) => string
13+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [namespaceStringToId](./kibana-plugin-core-server.namespacestringtoid.md)
4+
5+
## namespaceStringToId variable
6+
7+
Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the `'default'` namespace string (which has a namespace ID of `undefined`<!-- -->).
8+
9+
<b>Signature:</b>
10+
11+
```typescript
12+
namespaceStringToId: (namespace: string) => string | undefined
13+
```

src/core/server/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ export {
298298
exportSavedObjectsToStream,
299299
importSavedObjectsFromStream,
300300
resolveSavedObjectsImportErrors,
301+
namespaceIdToString,
302+
namespaceStringToId,
301303
} from './saved_objects';
302304

303305
export {

src/core/server/saved_objects/service/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export {
5858
SavedObjectsErrorHelpers,
5959
SavedObjectsClientFactory,
6060
SavedObjectsClientFactoryProvider,
61+
namespaceIdToString,
62+
namespaceStringToId,
6163
} from './lib';
6264

6365
export * from './saved_objects_client';

src/core/server/saved_objects/service/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ export {
3030
} from './scoped_client_provider';
3131

3232
export { SavedObjectsErrorHelpers } from './errors';
33+
34+
export { namespaceIdToString, namespaceStringToId } from './namespace';
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { namespaceIdToString, namespaceStringToId } from './namespace';
21+
22+
describe('#namespaceIdToString', () => {
23+
it('converts `undefined` to default namespace string', () => {
24+
expect(namespaceIdToString(undefined)).toEqual('default');
25+
});
26+
27+
it('leaves other namespace IDs as-is', () => {
28+
expect(namespaceIdToString('foo')).toEqual('foo');
29+
});
30+
31+
it('throws an error when a namespace ID is an empty string', () => {
32+
expect(() => namespaceIdToString('')).toThrowError('namespace cannot be an empty string');
33+
});
34+
});
35+
36+
describe('#namespaceStringToId', () => {
37+
it('converts default namespace string to `undefined`', () => {
38+
expect(namespaceStringToId('default')).toBeUndefined();
39+
});
40+
41+
it('leaves other namespace strings as-is', () => {
42+
expect(namespaceStringToId('foo')).toEqual('foo');
43+
});
44+
45+
it('throws an error when a namespace string is falsy', () => {
46+
const test = (arg: any) =>
47+
expect(() => namespaceStringToId(arg)).toThrowError('namespace must be a non-empty string');
48+
49+
test(undefined);
50+
test(null);
51+
test('');
52+
});
53+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
export const DEFAULT_NAMESPACE_STRING = 'default';
21+
22+
/**
23+
* @public
24+
* Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with
25+
* the exception of the `undefined` namespace ID (which has a namespace string of `'default'`).
26+
*
27+
* @param namespace The namespace ID, which must be either a non-empty string or `undefined`.
28+
*/
29+
export const namespaceIdToString = (namespace?: string) => {
30+
if (namespace === '') {
31+
throw new TypeError('namespace cannot be an empty string');
32+
}
33+
34+
return namespace ?? DEFAULT_NAMESPACE_STRING;
35+
};
36+
37+
/**
38+
* @public
39+
* Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with
40+
* the exception of the `'default'` namespace string (which has a namespace ID of `undefined`).
41+
*
42+
* @param namespace The namespace string, which must be non-empty.
43+
*/
44+
export const namespaceStringToId = (namespace: string) => {
45+
if (!namespace) {
46+
throw new TypeError('namespace must be a non-empty string');
47+
}
48+
49+
return namespace !== DEFAULT_NAMESPACE_STRING ? namespace : undefined;
50+
};

src/core/server/saved_objects/service/lib/repository.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
} from '../../types';
6464
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
6565
import { validateConvertFilterToKueryNode } from './filter_utils';
66+
import { namespaceIdToString } from './namespace';
6667

6768
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
6869
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
@@ -470,7 +471,7 @@ export class SavedObjectsRepository {
470471
preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace);
471472
const existingNamespaces = getSavedObjectNamespaces(undefined, preflightResult);
472473
const remainingNamespaces = existingNamespaces?.filter(
473-
(x) => x !== getNamespaceString(namespace)
474+
(x) => x !== namespaceIdToString(namespace)
474475
);
475476

476477
if (remainingNamespaces?.length) {
@@ -568,7 +569,7 @@ export class SavedObjectsRepository {
568569
}
569570
`,
570571
lang: 'painless',
571-
params: { namespace: getNamespaceString(namespace) },
572+
params: { namespace: namespaceIdToString(namespace) },
572573
},
573574
conflicts: 'proceed',
574575
...getSearchDsl(this._mappings, this._registry, {
@@ -793,7 +794,7 @@ export class SavedObjectsRepository {
793794

794795
let namespaces = [];
795796
if (!this._registry.isNamespaceAgnostic(type)) {
796-
namespaces = doc._source.namespaces ?? [getNamespaceString(doc._source.namespace)];
797+
namespaces = doc._source.namespaces ?? [namespaceIdToString(doc._source.namespace)];
797798
}
798799

799800
return {
@@ -849,7 +850,7 @@ export class SavedObjectsRepository {
849850

850851
let namespaces: string[] = [];
851852
if (!this._registry.isNamespaceAgnostic(type)) {
852-
namespaces = body._source.namespaces ?? [getNamespaceString(body._source.namespace)];
853+
namespaces = body._source.namespaces ?? [namespaceIdToString(body._source.namespace)];
853854
}
854855

855856
return {
@@ -922,7 +923,7 @@ export class SavedObjectsRepository {
922923

923924
let namespaces = [];
924925
if (!this._registry.isNamespaceAgnostic(type)) {
925-
namespaces = body.get._source.namespaces ?? [getNamespaceString(body.get._source.namespace)];
926+
namespaces = body.get._source.namespaces ?? [namespaceIdToString(body.get._source.namespace)];
926927
}
927928

928929
return {
@@ -1199,12 +1200,12 @@ export class SavedObjectsRepository {
11991200
};
12001201
}
12011202
namespaces = actualResult._source.namespaces ?? [
1202-
getNamespaceString(actualResult._source.namespace),
1203+
namespaceIdToString(actualResult._source.namespace),
12031204
];
12041205
versionProperties = getExpectedVersionProperties(version, actualResult);
12051206
} else {
12061207
if (this._registry.isSingleNamespace(type)) {
1207-
namespaces = [getNamespaceString(namespace)];
1208+
namespaces = [namespaceIdToString(namespace)];
12081209
}
12091210
versionProperties = getExpectedVersionProperties(version);
12101211
}
@@ -1391,7 +1392,7 @@ export class SavedObjectsRepository {
13911392
const savedObject = this._serializer.rawToSavedObject(raw);
13921393
const { namespace, type } = savedObject;
13931394
if (this._registry.isSingleNamespace(type)) {
1394-
savedObject.namespaces = [getNamespaceString(namespace)];
1395+
savedObject.namespaces = [namespaceIdToString(namespace)];
13951396
}
13961397
return omit(savedObject, 'namespace') as SavedObject<T>;
13971398
}
@@ -1414,7 +1415,7 @@ export class SavedObjectsRepository {
14141415
}
14151416

14161417
const namespaces = raw._source.namespaces;
1417-
return namespaces?.includes(getNamespaceString(namespace)) ?? false;
1418+
return namespaces?.includes(namespaceIdToString(namespace)) ?? false;
14181419
}
14191420

14201421
/**
@@ -1519,14 +1520,6 @@ function getExpectedVersionProperties(version?: string, document?: SavedObjectsR
15191520
return {};
15201521
}
15211522

1522-
/**
1523-
* Returns the string representation of a namespace.
1524-
* The default namespace is undefined, and is represented by the string 'default'.
1525-
*/
1526-
function getNamespaceString(namespace?: string) {
1527-
return namespace ?? 'default';
1528-
}
1529-
15301523
/**
15311524
* Returns a string array of namespaces for a given saved object. If the saved object is undefined, the result is an array that contains the
15321525
* current namespace. Value may be undefined if an existing saved object has no namespaces attribute; this should not happen in normal
@@ -1542,7 +1535,7 @@ function getSavedObjectNamespaces(
15421535
if (document) {
15431536
return document._source?.namespaces;
15441537
}
1545-
return [getNamespaceString(namespace)];
1538+
return [namespaceIdToString(namespace)];
15461539
}
15471540

15481541
const unique = (array: string[]) => [...new Set(array)];

src/core/server/saved_objects/service/lib/search_dsl/query_params.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { esKuery, KueryNode } from '../../../../../../plugins/data/server';
2121

2222
import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
2323
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
24+
import { DEFAULT_NAMESPACE_STRING } from '../namespace';
2425

2526
/**
2627
* Gets the types based on the type. Uses mappings to support
@@ -63,7 +64,7 @@ function getFieldsForTypes(types: string[], searchFields?: string[]) {
6364
*/
6465
function getClauseForType(
6566
registry: ISavedObjectTypeRegistry,
66-
namespaces: string[] = ['default'],
67+
namespaces: string[] = [DEFAULT_NAMESPACE_STRING],
6768
type: string
6869
) {
6970
if (namespaces.length === 0) {
@@ -78,11 +79,11 @@ function getClauseForType(
7879
};
7980
} else if (registry.isSingleNamespace(type)) {
8081
const should: Array<Record<string, any>> = [];
81-
const eligibleNamespaces = namespaces.filter((namespace) => namespace !== 'default');
82+
const eligibleNamespaces = namespaces.filter((x) => x !== DEFAULT_NAMESPACE_STRING);
8283
if (eligibleNamespaces.length > 0) {
8384
should.push({ terms: { namespace: eligibleNamespaces } });
8485
}
85-
if (namespaces.includes('default')) {
86+
if (namespaces.includes(DEFAULT_NAMESPACE_STRING)) {
8687
should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
8788
}
8889
if (should.length === 0) {
@@ -150,9 +151,7 @@ export function getQueryParams({
150151
// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
151152
// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
152153
const normalizedNamespaces = namespaces
153-
? Array.from(
154-
new Set(namespaces.map((namespace) => (namespace === '*' ? 'default' : namespace)))
155-
)
154+
? Array.from(new Set(namespaces.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))))
156155
: undefined;
157156

158157
const bool: any = {

0 commit comments

Comments
 (0)