Skip to content

Commit

Permalink
Merge branch 'master' into issue-88744-sharing-saved-objects-guide
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Aug 10, 2021
2 parents 469147b + 4f7e62f commit 048ee0a
Show file tree
Hide file tree
Showing 327 changed files with 6,076 additions and 7,356 deletions.
21 changes: 12 additions & 9 deletions dev_docs/tutorials/saved_objects.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Since Elasticsearch has a default limit of 1000 fields per index, plugins should
fields they add to the mappings. Similarly, Saved Object types should never use `dynamic: true` as this can cause an arbitrary
amount of fields to be added to the .kibana index.

## References
## References

Declare <DocLink id="kibDevDocsSavedObjectsIntro" section="References" text="Saved Object references"/> by adding an id, type and name to the
`references` array.
Expand Down Expand Up @@ -159,12 +159,14 @@ identify this reference. This guarantees that the id the reference points to alw
visualization id was directly stored in `dashboard.panels[0].visualization` there is a risk that this id gets updated without
updating the reference in the references array.

## Writing migrations
## Migrations

Saved Objects support schema changes between Kibana versions, which we call migrations. Migrations are
applied when a Kibana installation is upgraded from one version to the next, when exports are imported via
applied when a Kibana installation is upgraded from one version to a newer version, when exports are imported via
the Saved Objects Management UI, or when a new object is created via the HTTP API.

### Writing migrations

Each Saved Object type may define migrations for its schema. Migrations are specified by the Kibana version number, receive an input document,
and must return the fully migrated document to be persisted to Elasticsearch.

Expand Down Expand Up @@ -245,10 +247,11 @@ export const dashboardVisualization: SavedObjectsType = {
in which this migration was released. So if you are creating a migration which will
be part of the v7.10.0 release, but will also be backported and released as v7.9.3, the migration version should be: 7.9.3.

Migrations should be written defensively, an exception in a migration function will prevent a Kibana upgrade from succeeding and will cause downtime for our users.
Having said that, if a
document is encountered that is not in the expected shape, migrations are encouraged to throw an exception to abort the upgrade. In most scenarios, it is better to
fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time.
Migrations should be written defensively, an exception in a migration function will prevent a Kibana upgrade from succeeding and will cause downtime for our users.
Having said that, if a document is encountered that is not in the expected shape, migrations are encouraged to throw an exception to abort the upgrade. In most scenarios, it is better to
fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time. When such a scenario is encountered,
the error should be verbose and informative so that the corrupt document can be corrected, if possible.

### Testing Migrations

It is critical that you have extensive tests to ensure that migrations behave as expected with all possible input documents. Given how simple it is to test all the branch
conditions in a migration function and the high impact of a bug in this code, there’s really no reason not to aim for 100% test code coverage.
Bugs in a migration function cause downtime for our users and therefore have a very high impact. Follow the <DocLink id="kibDevTutorialTestingPlugins" section="saved-object-migrations" text="Saved Object migrations section in the plugin testing guide"/>.
188 changes: 187 additions & 1 deletion dev_docs/tutorials/testing_plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ describe('renderApp', () => {
});
```
### SavedObjects
### SavedObjectsClient
#### Unit Tests
Expand Down Expand Up @@ -794,6 +794,192 @@ Kibana and esArchiver to load fixture data into Elasticsearch.
_todo: fully worked out example_
### Saved Objects migrations
_Also see <DocLink id="kibDevTutorialSavedObject" section="migrations" text="How to write a migration"/>._
It is critical that you have extensive tests to ensure that migrations behave as expected with all possible input
documents. Given how simple it is to test all the branch conditions in a migration function and the high impact of a
bug in this code, there’s really no reason not to aim for 100% test code coverage.
It's recommend that you primarily leverage unit testing with Jest for testing your migration function. Unit tests will
be a much more effective approach to testing all the different shapes of input data and edge cases that your migration
may need to handle. With more complex migrations that interact with several components or may behave different depending
on registry contents (such as Embeddable migrations), we recommend that you use the Jest Integration suite which allows
you to create a full instance Kibana and all plugins in memory and leverage the import API to test migrating documents.
#### Throwing exceptions
Keep in mind that any exception thrown by your migration function will cause Kibana to fail to upgrade. This should almost
never happen for our end users and we should be exhaustive in our testing to be sure to catch as many edge cases that we
could possibly handle. This entails ensuring that the migration is written defensively; we should try to avoid every bug
possible in our implementation.
In general, exceptions should only be thrown when the input data is corrupted and doesn't match the expected schema. In
such cases, it's important that an informative error message is included in the exception and we do not rely on implicit
runtime exceptions such as "null pointer exceptions" like `TypeError: Cannot read property 'foo' of undefined`.
#### Unit testing
Unit testing migration functions is typically pretty straight forward and comparable to other types of Jest testing. In
general, you should focus this tier of testing on validating output and testing input edge cases. One focus of this tier
should be trying to find edge cases that throw exceptions the migration shouldn't. As you can see in this simple
example, the coverage here is very exhaustive and verbose, which is intentional.
```ts
import { migrateCaseFromV7_9_0ToV7_10_0 } from './case_migrations';

const validInput_7_9_0 = {
id: '1',
type: 'case',
attributes: {
connector_id: '1234';
}
}

describe('Case migrations v7.7.0 -> v7.8.0', () => {
it('transforms the connector field', () => {
expect(migrateCaseFromV7_9_0ToV7_10_0(validInput_7_9_0)).toEqual({
id: '1',
type: 'case',
attributes: {
connector: {
id: '1234', // verify id was moved into subobject
name: 'none', // verify new default field was added
}
}
});
});

it('handles empty string', () => {
expect(migrateCaseFromV7_9_0ToV7_10_0({
id: '1',
type: 'case',
attributes: {
connector_id: ''
}
})).toEqual({
id: '1',
type: 'case',
attributes: {
connector: {
id: 'none',
name: 'none',
}
}
});
});

it('handles null', () => {
expect(migrateCaseFromV7_9_0ToV7_10_0({
id: '1',
type: 'case',
attributes: {
connector_id: null
}
})).toEqual({
id: '1',
type: 'case',
attributes: {
connector: {
id: 'none',
name: 'none',
}
}
});
});

it('handles undefined', () => {
expect(migrateCaseFromV7_9_0ToV7_10_0({
id: '1',
type: 'case',
attributes: {
// Even though undefined isn't a valid JSON or Elasticsearch value, we should test it anyways since there
// could be some JavaScript layer that casts the field to `undefined` for some reason.
connector_id: undefined
}
})).toEqual({
id: '1',
type: 'case',
attributes: {
connector: {
id: 'none',
name: 'none',
}
}
});

expect(migrateCaseFromV7_9_0ToV7_10_0({
id: '1',
type: 'case',
attributes: {
// also test without the field present at all
}
})).toEqual({
id: '1',
type: 'case',
attributes: {
connector: {
id: 'none',
name: 'none',
}
}
});
});
});
```
#### Integration testing
With more complicated migrations, the behavior of the migration may be dependent on values from other plugins which may
be difficult or even impossible to test with unit tests. You need to actually bootstrap Kibana, load the plugins, and
then test the full end-to-end migration. This type of set up will also test ingesting your documents into Elasticsearch
against the mappings defined by your Saved Object type.
This can be achieved using the `jest_integration` suite and the `kbnTestServer` utility for starting an in-memory
instance of Kibana. You can then leverage the import API to test migrations. This API applies the same migrations to
imported documents as are applied at Kibana startup and is much easier to work with for testing.
```ts
// You may need to adjust these paths depending on where your test file is located.
// The absolute path is src/core/test_helpers/so_migrations
import { createTestHarness, SavedObjectTestHarness } from '../../../../src/core/test_helpers/so_migrations';

describe('my plugin migrations', () => {
let testHarness: SavedObjectTestHarness;

beforeAll(async () => {
testHarness = createTestHarness();
await testHarness.start();
});

afterAll(async () => {
await testHarness.stop();
});

it('successfully migrates valid case documents', async () => {
expect(
await testHarness.migrate([
{ type: 'case', id: '1', attributes: { connector_id: '1234' }, references: [] },
{ type: 'case', id: '2', attributes: { connector_id: '' }, references: [] },
{ type: 'case', id: '3', attributes: { connector_id: null }, references: [] },
])
).toEqual([
expect.objectContaining(
{ type: 'case', id: '1', attributes: { connector: { id: '1234', name: 'none' } } }),
expect.objectContaining(
{ type: 'case', id: '2', attributes: { connector: { id: 'none', name: 'none' } } }),
expect.objectContaining(
{ type: 'case', id: '3', attributes: { connector: { id: 'none', name: 'none' } } }),
])
})
})
```
There are some caveats about using the import/export API for testing migrations:
- You cannot test the startup behavior of Kibana this way. This should not have any effect on type migrations but does
mean that this method cannot be used for testing the migration algorithm itself.
- While not yet supported, if support is added for migrations that affect multiple types, it's possible that the
behavior during import may vary slightly from the upgrade behavior.
### Elasticsearch
_How to test ES clients_
Expand Down
2 changes: 0 additions & 2 deletions docs/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ This section summarizes the changes in each release.
[[release-notes-8.0.0-alpha1]]
== {kib} 8.0.0-alpha1

coming[8.0.0]

The following changes are released for the first time in {kib} 8.0.0-alpha1. Review the changes, then use the <<upgrade-assistant,Upgrade Assistant>> to complete the upgrade.

[float]
Expand Down
32 changes: 17 additions & 15 deletions docs/maps/import-geospatial-data.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ To import geospatical data into the Elastic Stack, the data must be indexed as {
Geospatial data comes in many formats.
Choose an import tool based on the format of your geospatial data.

TIP: When you upload GeoJSON or delimited files in {kib}, there is a file size
limit, which is configurable in <<fileupload-maxfilesize,Advanced Settings>>.

[discrete]
[[import-geospatial-privileges]]
=== Security privileges
Expand All @@ -18,37 +21,36 @@ spaces in **{stack-manage-app}** in {kib}. For more information, see

To upload GeoJSON files in {kib} with *Maps*, you must have:

* The `all` {kib} privilege for *Maps*.
* The `all` {kib} privilege for *Index Pattern Management*.
* The `create` and `create_index` index privileges for destination indices.
* To use the index in *Maps*, you must also have the `read` and `view_index_metadata` index privileges for destination indices.
* The `all` {kib} privilege for *Maps*
* The `all` {kib} privilege for *{ipm-app}*
* The `create` and `create_index` index privileges for destination indices
* To use the index in *Maps*, you must also have the `read` and `view_index_metadata` index privileges for destination indices

To upload CSV files in {kib} with the *{file-data-viz}*, you must have privileges to upload GeoJSON files and:
To upload delimited files (such as CSV, TSV, or JSON files) on the {kib} home page, you must also have:

* The `manage_pipeline` cluster privilege.
* The `read` {kib} privilege for *Machine Learning*.
* The `machine_learning_admin` or `machine_learning_user` role.
* The `all` {kib} privilege for *Discover*
* The `manage_pipeline` or `manage_ingest_pipelines` cluster privilege
* The `manage` index privilege for destination indices


[discrete]
=== Upload CSV with latitude and longitude columns
=== Upload delimited files with latitude and longitude columns

*File Data Visualizer* indexes CSV files with latitude and longitude columns as a geo_point.
On the {kib} home page, you can upload a file and import it into an {es} index with latitude and longitude columns combined into a `geo_point` field.

. Open the main menu, then click *Machine Learning*.
. Select the *Data Visualizer* tab, then click *Upload file*.
. Use the file chooser to select a CSV file.
. Go to the {kib} home page and click *Upload a file*.
. Select a file in one of the supported file formats.
. Click *Import*.
. Select the *Advanced* tab.
. Set *Index name*.
. Click *Add combined field*, then click *Add geo point field*.
. If a combined `geo_point` field is not created automatically, click *Add combined field*, then click *Add geo point field*.
. Fill out the form and click *Add*.
. Click *Import*.

[discrete]
=== Upload a GeoJSON file

*Upload GeoJSON* indexes GeoJSON features as a geo_point or geo_shape.
*Upload GeoJSON* indexes GeoJSON features as a `geo_point` or `geo_shape`.

. <<maps-create, Create a new map>>.
. Click *Add layer*.
Expand Down
9 changes: 7 additions & 2 deletions packages/kbn-test/src/jest/utils/get_url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ interface UrlParam {
username?: string;
}

interface App {
pathname?: string;
hash?: string;
}

/**
* Converts a config and a pathname to a url
* @param {object} config A url config
Expand All @@ -41,11 +46,11 @@ interface UrlParam {
* @return {string}
*/

function getUrl(config: UrlParam, app: UrlParam) {
function getUrl(config: UrlParam, app: App) {
return url.format(_.assign({}, config, app));
}

getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: UrlParam) {
getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: App) {
config = _.pickBy(config, function (val, param) {
return param !== 'auth';
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const REMOVED_TYPES: string[] = [
'server',
// https://github.com/elastic/kibana/issues/95617
'tsvb-validation-telemetry',
// replaced by osquery-manager-usage-metric
'osquery-usage-metric',
].sort();

// When migrating from the outdated index we use a read query which excludes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const previouslyRegisteredTypes = [
'monitoring-telemetry',
'osquery-saved-query',
'osquery-usage-metric',
'osquery-manager-usage-metric',
'query',
'sample-data-telemetry',
'search',
Expand Down
13 changes: 11 additions & 2 deletions src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import { DiscoverServices } from '../build_services';
import { dataPluginMock } from '../../../data/public/mocks';
import { chromeServiceMock, coreMock, docLinksServiceMock } from '../../../../core/public/mocks';
import {
CONTEXT_STEP_SETTING,
DEFAULT_COLUMNS_SETTING,
DOC_HIDE_TIME_COLUMN_SETTING,
SAMPLE_SIZE_SETTING,
SORT_DEFAULT_ORDER_SETTING,
} from '../../common';
import { savedSearchMock } from './saved_search';
import { UI_SETTINGS } from '../../../data/common';
import { TopNavMenu } from '../../../navigation/public';
import { FORMATS_UI_SETTINGS } from 'src/plugins/field_formats/common';
const dataPlugin = dataPluginMock.createStartContract();

export const discoverServiceMock = ({
Expand Down Expand Up @@ -49,10 +52,16 @@ export const discoverServiceMock = ({
return [];
} else if (key === UI_SETTINGS.META_FIELDS) {
return [];
} else if (key === SAMPLE_SIZE_SETTING) {
return 250;
} else if (key === DOC_HIDE_TIME_COLUMN_SETTING) {
return false;
} else if (key === CONTEXT_STEP_SETTING) {
return 5;
} else if (key === SORT_DEFAULT_ORDER_SETTING) {
return 'desc';
} else if (key === FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE) {
return false;
} else if (key === SAMPLE_SIZE_SETTING) {
return 250;
}
},
isDefault: (key: string) => {
Expand Down
Loading

0 comments on commit 048ee0a

Please sign in to comment.