Skip to content

Commit

Permalink
Add namespaceStringToId and namespaceIdToString methods to core
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jportner committed Aug 19, 2020
1 parent 0c6f682 commit 98ee331
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 76 deletions.
2 changes: 2 additions & 0 deletions docs/development/core/server/kibana-plugin-core-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| Variable | Description |
| --- | --- |
| [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. |
| [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>). |
| [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>). |
| [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. |
| [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [namespaceIdToString](./kibana-plugin-core-server.namespaceidtostring.md)

## namespaceIdToString variable

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'`<!-- -->).

<b>Signature:</b>

```typescript
namespaceIdToString: (namespace?: string | undefined) => string
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [namespaceStringToId](./kibana-plugin-core-server.namespacestringtoid.md)

## namespaceStringToId variable

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`<!-- -->).

<b>Signature:</b>

```typescript
namespaceStringToId: (namespace: string) => string | undefined
```
2 changes: 2 additions & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ export {
exportSavedObjectsToStream,
importSavedObjectsFromStream,
resolveSavedObjectsImportErrors,
namespaceIdToString,
namespaceStringToId,
} from './saved_objects';

export {
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/saved_objects/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export {
SavedObjectsErrorHelpers,
SavedObjectsClientFactory,
SavedObjectsClientFactoryProvider,
namespaceIdToString,
namespaceStringToId,
} from './lib';

export * from './saved_objects_client';
2 changes: 2 additions & 0 deletions src/core/server/saved_objects/service/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ export {
} from './scoped_client_provider';

export { SavedObjectsErrorHelpers } from './errors';

export { namespaceIdToString, namespaceStringToId } from './namespace';
53 changes: 53 additions & 0 deletions src/core/server/saved_objects/service/lib/namespace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { namespaceIdToString, namespaceStringToId } from './namespace';

describe('#namespaceIdToString', () => {
it('converts `undefined` to default namespace string', () => {
expect(namespaceIdToString(undefined)).toEqual('default');
});

it('leaves other namespace IDs as-is', () => {
expect(namespaceIdToString('foo')).toEqual('foo');
});

it('throws an error when a namespace ID is an empty string', () => {
expect(() => namespaceIdToString('')).toThrowError('namespace cannot be an empty string');
});
});

describe('#namespaceStringToId', () => {
it('converts default namespace string to `undefined`', () => {
expect(namespaceStringToId('default')).toBeUndefined();
});

it('leaves other namespace strings as-is', () => {
expect(namespaceStringToId('foo')).toEqual('foo');
});

it('throws an error when a namespace string is falsy', () => {
const test = (arg: any) =>
expect(() => namespaceStringToId(arg)).toThrowError('namespace must be a non-empty string');

test(undefined);
test(null);
test('');
});
});
50 changes: 50 additions & 0 deletions src/core/server/saved_objects/service/lib/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export const DEFAULT_NAMESPACE_STRING = 'default';

/**
* @public
* 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'`).
*
* @param namespace The namespace ID, which must be either a non-empty string or `undefined`.
*/
export const namespaceIdToString = (namespace?: string) => {
if (namespace === '') {
throw new TypeError('namespace cannot be an empty string');
}

return namespace ?? DEFAULT_NAMESPACE_STRING;
};

/**
* @public
* 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`).
*
* @param namespace The namespace string, which must be non-empty.
*/
export const namespaceStringToId = (namespace: string) => {
if (!namespace) {
throw new TypeError('namespace must be a non-empty string');
}

return namespace !== DEFAULT_NAMESPACE_STRING ? namespace : undefined;
};
29 changes: 11 additions & 18 deletions src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
} from '../../types';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { validateConvertFilterToKueryNode } from './filter_utils';
import { namespaceIdToString } from './namespace';

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

if (remainingNamespaces?.length) {
Expand Down Expand Up @@ -568,7 +569,7 @@ export class SavedObjectsRepository {
}
`,
lang: 'painless',
params: { namespace: getNamespaceString(namespace) },
params: { namespace: namespaceIdToString(namespace) },
},
conflicts: 'proceed',
...getSearchDsl(this._mappings, this._registry, {
Expand Down Expand Up @@ -793,7 +794,7 @@ export class SavedObjectsRepository {

let namespaces = [];
if (!this._registry.isNamespaceAgnostic(type)) {
namespaces = doc._source.namespaces ?? [getNamespaceString(doc._source.namespace)];
namespaces = doc._source.namespaces ?? [namespaceIdToString(doc._source.namespace)];
}

return {
Expand Down Expand Up @@ -849,7 +850,7 @@ export class SavedObjectsRepository {

let namespaces: string[] = [];
if (!this._registry.isNamespaceAgnostic(type)) {
namespaces = body._source.namespaces ?? [getNamespaceString(body._source.namespace)];
namespaces = body._source.namespaces ?? [namespaceIdToString(body._source.namespace)];
}

return {
Expand Down Expand Up @@ -922,7 +923,7 @@ export class SavedObjectsRepository {

let namespaces = [];
if (!this._registry.isNamespaceAgnostic(type)) {
namespaces = body.get._source.namespaces ?? [getNamespaceString(body.get._source.namespace)];
namespaces = body.get._source.namespaces ?? [namespaceIdToString(body.get._source.namespace)];
}

return {
Expand Down Expand Up @@ -1199,12 +1200,12 @@ export class SavedObjectsRepository {
};
}
namespaces = actualResult._source.namespaces ?? [
getNamespaceString(actualResult._source.namespace),
namespaceIdToString(actualResult._source.namespace),
];
versionProperties = getExpectedVersionProperties(version, actualResult);
} else {
if (this._registry.isSingleNamespace(type)) {
namespaces = [getNamespaceString(namespace)];
namespaces = [namespaceIdToString(namespace)];
}
versionProperties = getExpectedVersionProperties(version);
}
Expand Down Expand Up @@ -1391,7 +1392,7 @@ export class SavedObjectsRepository {
const savedObject = this._serializer.rawToSavedObject(raw);
const { namespace, type } = savedObject;
if (this._registry.isSingleNamespace(type)) {
savedObject.namespaces = [getNamespaceString(namespace)];
savedObject.namespaces = [namespaceIdToString(namespace)];
}
return omit(savedObject, 'namespace') as SavedObject<T>;
}
Expand All @@ -1414,7 +1415,7 @@ export class SavedObjectsRepository {
}

const namespaces = raw._source.namespaces;
return namespaces?.includes(getNamespaceString(namespace)) ?? false;
return namespaces?.includes(namespaceIdToString(namespace)) ?? false;
}

/**
Expand Down Expand Up @@ -1519,14 +1520,6 @@ function getExpectedVersionProperties(version?: string, document?: SavedObjectsR
return {};
}

/**
* Returns the string representation of a namespace.
* The default namespace is undefined, and is represented by the string 'default'.
*/
function getNamespaceString(namespace?: string) {
return namespace ?? 'default';
}

/**
* 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
* current namespace. Value may be undefined if an existing saved object has no namespaces attribute; this should not happen in normal
Expand All @@ -1542,7 +1535,7 @@ function getSavedObjectNamespaces(
if (document) {
return document._source?.namespaces;
}
return [getNamespaceString(namespace)];
return [namespaceIdToString(namespace)];
}

const unique = (array: string[]) => [...new Set(array)];
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { esKuery, KueryNode } from '../../../../../../plugins/data/server';

import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { DEFAULT_NAMESPACE_STRING } from '../namespace';

/**
* Gets the types based on the type. Uses mappings to support
Expand Down Expand Up @@ -63,7 +64,7 @@ function getFieldsForTypes(types: string[], searchFields?: string[]) {
*/
function getClauseForType(
registry: ISavedObjectTypeRegistry,
namespaces: string[] = ['default'],
namespaces: string[] = [DEFAULT_NAMESPACE_STRING],
type: string
) {
if (namespaces.length === 0) {
Expand All @@ -78,11 +79,11 @@ function getClauseForType(
};
} else if (registry.isSingleNamespace(type)) {
const should: Array<Record<string, any>> = [];
const eligibleNamespaces = namespaces.filter((namespace) => namespace !== 'default');
const eligibleNamespaces = namespaces.filter((x) => x !== DEFAULT_NAMESPACE_STRING);
if (eligibleNamespaces.length > 0) {
should.push({ terms: { namespace: eligibleNamespaces } });
}
if (namespaces.includes('default')) {
if (namespaces.includes(DEFAULT_NAMESPACE_STRING)) {
should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
}
if (should.length === 0) {
Expand Down Expand Up @@ -150,9 +151,7 @@ export function getQueryParams({
// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
const normalizedNamespaces = namespaces
? Array.from(
new Set(namespaces.map((namespace) => (namespace === '*' ? 'default' : namespace)))
)
? Array.from(new Set(namespaces.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))))
: undefined;

const bool: any = {
Expand Down
6 changes: 6 additions & 0 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,12 @@ export function modifyUrl(url: string, urlModifier: (urlParts: URLMeaningfulPart
// @public
export type MutatingOperationRefreshSetting = boolean | 'wait_for';

// @public
export const namespaceIdToString: (namespace?: string | undefined) => string;

// @public
export const namespaceStringToId: (namespace: string) => string | undefined;

// Warning: (ae-missing-release-tag) "NodesVersionCompatibility" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/spaces/server/lib/utils/__mocks__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

const mockNamespaceIdToString = jest.fn();
const mockNamespaceStringToId = jest.fn();
jest.mock('../../../../../../../src/core/server', () => ({
namespaceIdToString: mockNamespaceIdToString,
namespaceStringToId: mockNamespaceStringToId,
}));

export { mockNamespaceIdToString, mockNamespaceStringToId };
Loading

0 comments on commit 98ee331

Please sign in to comment.