Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SavedObjectsRepository.incrementCounter supports array of fields #84326

Merged
merged 9 commits into from
Dec 1, 2020
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; [SavedObjectsIncrementCounterOptions](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.md) &gt; [initialize](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md)

## SavedObjectsIncrementCounterOptions.initialize property

(default=false) If true, sets all the counter fields to 0 if they don't already exist. Existing fields will be left as-is and won't be incremented.

<b>Signature:</b>

```typescript
initialize?: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt

| Property | Type | Description |
| --- | --- | --- |
| [migrationVersion](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md) | <code>SavedObjectsMigrationVersion</code> | |
| [refresh](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md) | <code>MutatingOperationRefreshSetting</code> | The Elasticsearch Refresh setting for this operation |
| [initialize](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md) | <code>boolean</code> | (default=false) If true, sets all the counter fields to 0 if they don't already exist. Existing fields will be left as-is and won't be incremented. |
| [migrationVersion](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md) | <code>SavedObjectsMigrationVersion</code> | [SavedObjectsMigrationVersion](./kibana-plugin-core-server.savedobjectsmigrationversion.md) |
| [refresh](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md) | <code>MutatingOperationRefreshSetting</code> | (default='wait\_for') The Elasticsearch refresh setting for this operation. See [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) |

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## SavedObjectsIncrementCounterOptions.migrationVersion property

[SavedObjectsMigrationVersion](./kibana-plugin-core-server.savedobjectsmigrationversion.md)

<b>Signature:</b>

```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## SavedObjectsIncrementCounterOptions.refresh property

The Elasticsearch Refresh setting for this operation
(default='wait\_for') The Elasticsearch refresh setting for this operation. See [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md)

<b>Signature:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,53 @@

## SavedObjectsRepository.incrementCounter() method

Increases a counter field by one. Creates the document if one doesn't exist for the given id.
Increments all the specified counter fields by one. Creates the document if one doesn't exist for the given id.

<b>Signature:</b>

```typescript
incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<SavedObject>;
incrementCounter(type: string, id: string, counterFieldNames: string[], options?: SavedObjectsIncrementCounterOptions): Promise<SavedObject>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| type | <code>string</code> | |
| id | <code>string</code> | |
| counterFieldName | <code>string</code> | |
| options | <code>SavedObjectsIncrementCounterOptions</code> | |
| type | <code>string</code> | The type of saved object whose fields should be incremented |
| id | <code>string</code> | The id of the document whose fields should be incremented |
| counterFieldNames | <code>string[]</code> | An array of field names to increment |
| options | <code>SavedObjectsIncrementCounterOptions</code> | [SavedObjectsIncrementCounterOptions](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.md) |

<b>Returns:</b>

`Promise<SavedObject>`

{<!-- -->promise<!-- -->}
The saved object after the specified fields were incremented

## Remarks

When supplying a field name like `stats.api.counter` the field name will be used as-is to create a document like: `{attributes: {'stats.api.counter': 1}}` It will not create a nested structure like: `{attributes: {stats: {api: {counter: 1}}}}`

When using incrementCounter for collecting usage data, you need to ensure that usage collection happens on a best-effort basis and doesn't negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/master/src/plugins/usage\_collection/README.md\#tracking-interactions-with-incrementcounter)

## Example


```ts
const repository = coreStart.savedObjects.createInternalRepository();

// Initialize all fields to 0
repository
.incrementCounter('dashboard_counter_type', 'counter_id', [
'stats.apiCalls',
'stats.sampleDataInstalled',
], {initialize: true});

// Increment the apiCalls field counter
repository
.incrementCounter('dashboard_counter_type', 'counter_id', [
'stats.apiCalls',
])

```

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export declare class SavedObjectsRepository
| [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[<code>addToNamespaces</code>\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. |
| [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | |
| [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object |
| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. |
| [incrementCounter(type, id, counterFieldNames, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increments all the specified counter fields by one. Creates the document if one doesn't exist for the given id. |
| [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.removereferencesto.md) | | Updates all objects containing a reference to the given {<!-- -->type, id<!-- -->} tuple to remove the said reference. |
| [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object |

Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* 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 { InternalCoreStart } from 'src/core/server/internal_types';
import * as kbnTestServer from '../../../../../test_helpers/kbn_server';
import { Root } from '../../../../root';

const { startES } = kbnTestServer.createTestServers({
adjustTimeout: (t: number) => jest.setTimeout(t),
});
let esServer: kbnTestServer.TestElasticsearchUtils;

describe('SavedObjectsRepository', () => {
let root: Root;
let start: InternalCoreStart;

beforeAll(async () => {
esServer = await startES();
root = kbnTestServer.createRootWithCorePlugins({
server: {
basePath: '/hello',
},
});

const setup = await root.setup();
setup.savedObjects.registerType({
hidden: false,
mappings: {
dynamic: false,
properties: {},
},
name: 'test_counter_type',
namespaceType: 'single',
});
start = await root.start();
});

afterAll(async () => {
await esServer.stop();
await root.shutdown();
});

describe('#incrementCounter', () => {
describe('initialize=false', () => {
it('creates a new document if none exists and sets all counter fields set to 1', async () => {
const now = new Date().getTime();
const repository = start.savedObjects.createInternalRepository();
await repository.incrementCounter('test_counter_type', 'counter_1', [
'stats.api.count',
'stats.api.count2',
'stats.total',
]);
const result = await repository.get('test_counter_type', 'counter_1');
expect(result.attributes).toMatchInlineSnapshot(`
Object {
"stats.api.count": 1,
"stats.api.count2": 1,
"stats.total": 1,
}
`);
expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now);
});
it('increments the specified counters of an existing document', async () => {
const repository = start.savedObjects.createInternalRepository();
// Create document
await repository.incrementCounter('test_counter_type', 'counter_2', [
'stats.api.count',
'stats.api.count2',
'stats.total',
]);

const now = new Date().getTime();
// Increment counters
await repository.incrementCounter('test_counter_type', 'counter_2', [
'stats.api.count',
'stats.api.count2',
'stats.total',
]);
const result = await repository.get('test_counter_type', 'counter_2');
expect(result.attributes).toMatchInlineSnapshot(`
Object {
"stats.api.count": 2,
"stats.api.count2": 2,
"stats.total": 2,
}
`);
expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now);
});
});
describe('initialize=true', () => {
it('creates a new document if none exists and sets all counter fields to 0', async () => {
const now = new Date().getTime();
const repository = start.savedObjects.createInternalRepository();
await repository.incrementCounter(
'test_counter_type',
'counter_3',
['stats.api.count', 'stats.api.count2', 'stats.total'],
{ initialize: true }
);
const result = await repository.get('test_counter_type', 'counter_3');
expect(result.attributes).toMatchInlineSnapshot(`
Object {
"stats.api.count": 0,
"stats.api.count2": 0,
"stats.total": 0,
}
`);
expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now);
});
it('sets any undefined counter fields to 0 but does not alter existing fields of an existing document', async () => {
const repository = start.savedObjects.createInternalRepository();
// Create document
await repository.incrementCounter('test_counter_type', 'counter_4', [
'stats.existing_field',
]);

const now = new Date().getTime();
// Initialize counters
await repository.incrementCounter(
'test_counter_type',
'counter_4',
['stats.existing_field', 'stats.new_field'],
{ initialize: true }
);
const result = await repository.get('test_counter_type', 'counter_4');
expect(result.attributes).toMatchInlineSnapshot(`
Object {
"stats.existing_field": 1,
"stats.new_field": 0,
}
`);
expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now);
});
});
});
});
Loading