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

Disable checking for conflicts when copying saved objects #83575

Merged
merged 29 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
40a9f4c
Add Spaces telemetry data
jportner Nov 17, 2020
10998e4
Disable checking for conflicts by default when copying objects
jportner Nov 17, 2020
e8ded81
Add Core telemetry data
jportner Nov 18, 2020
ecabf59
Disable checking for conflicts by default when importing objects
jportner Nov 19, 2020
25ee394
Merge branch 'master' into issue-81907-and-82324
kibanamachine Nov 23, 2020
7ecb815
PR review feedback
jportner Nov 23, 2020
e716476
i18n check
jportner Nov 23, 2020
8276465
Rename "telemetry data" to "usage stats"
jportner Nov 24, 2020
358940c
Rename usage stats fields
jportner Nov 24, 2020
fc123a1
node scripts/check_published_api_changes.js --accept
jportner Nov 24, 2020
b2bd013
Merge branch 'master' into pr/jportner/83575
jportner Nov 24, 2020
8bfee38
Tweak usage stats client unit tests
jportner Nov 24, 2020
c526e62
More PR review feedback
jportner Nov 24, 2020
ec2a77c
Merge branch 'master' into pr/jportner/83575
jportner Nov 24, 2020
f9b89a5
Revert "Disable checking for conflicts by default when importing obje…
jportner Nov 24, 2020
9cf6288
Add usage stats fields to differentiate first-party requests
jportner Nov 24, 2020
7129c3b
Merge branch 'master' into issue-81907-and-82324
kibanamachine Nov 29, 2020
b9428d3
Merge branch 'master' into pr/jportner/83575
jportner Nov 30, 2020
cfcf686
Even more PR review feedback
jportner Nov 30, 2020
eb78588
Merge branch 'master' into pr/jportner/83575
jportner Dec 1, 2020
69d7369
Refactor
jportner Dec 1, 2020
60c3820
Use SavedObjectsRepository.incrementCounter()
jportner Dec 1, 2020
d1124f8
Merge branch 'master' into pr/jportner/83575
jportner Dec 1, 2020
07cb882
node scripts/check_published_api_changes.js --accept
jportner Dec 1, 2020
787ee5e
Fix usage stats fields
jportner Dec 2, 2020
33b32bb
Merge branch 'master' into pr/jportner/83575
jportner Dec 2, 2020
8cd3433
node scripts/check_published_api_changes.js --accept
jportner Dec 2, 2020
21188ed
Merge branch 'master' into pr/jportner/83575
jportner Dec 3, 2020
60de78c
Apply suggestions from code review
jportner Dec 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions docs/api/spaces-management/copy_saved_objects.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,17 @@ You can request to overwrite any objects that already exist in the target space
(Optional, boolean) When set to `true`, all saved objects related to the specified saved objects will also be copied into the target
spaces. The default value is `false`.

`createNewCopies`::
(Optional, boolean) Creates new copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict
errors are avoided. The default value is `true`.
+
NOTE: This cannot be used with the `overwrite` option.
Comment on lines +54 to +58
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow I forgot to document this when I added this option 🙈 Added it now!


`overwrite`::
(Optional, boolean) When set to `true`, all conflicts are automatically overidden. When a saved object with a matching `type` and `id`
exists in the target space, that version is replaced with the version from the source space. The default value is `false`.
+
NOTE: This cannot be used with the `createNewCopies` option.

[role="child_attributes"]
[[spaces-api-copy-saved-objects-response-body]]
Expand Down Expand Up @@ -128,8 +136,7 @@ $ curl -X POST api/spaces/_copy_saved_objects
"id": "my-dashboard"
}],
"spaces": ["marketing"],
"includeReferences": true,
"createNewcopies": true
"includeReferences": true
}
----
// KIBANA
Expand Down Expand Up @@ -193,7 +200,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
"id": "my-dashboard"
}],
"spaces": ["marketing"],
"includeReferences": true
"includeReferences": true,
"createNewCopies": false
}
----
// KIBANA
Expand Down Expand Up @@ -254,7 +262,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
"id": "my-dashboard"
}],
"spaces": ["marketing", "sales"],
"includeReferences": true
"includeReferences": true,
"createNewCopies": false
}
----
// KIBANA
Expand Down Expand Up @@ -405,7 +414,8 @@ $ curl -X POST api/spaces/_copy_saved_objects
"id": "my-dashboard"
}],
"spaces": ["marketing"],
"includeReferences": true
"includeReferences": true,
"createNewCopies": false
}
----
// KIBANA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ Execute the <<spaces-api-copy-saved-objects,copy saved objects to space API>>, w
`includeReferences`::
(Optional, boolean) When set to `true`, all saved objects related to the specified saved objects are copied into the target spaces. The `includeReferences` must be the same values used during the failed <<spaces-api-copy-saved-objects, copy saved objects to space API>> operation. The default value is `false`.

`createNewCopies`::
(Optional, boolean) Creates new copies of the saved objects, regenerates each object ID, and resets the origin. When enabled during the
initial copy, also enable when resolving copy errors. The default value is `true`.

`retries`::
(Required, object) The retry operations to attempt, which can specify how to resolve different types of errors. Object keys represent the
target space IDs.
Expand Down Expand Up @@ -148,6 +152,7 @@ $ curl -X POST api/spaces/_resolve_copy_saved_objects_errors
"id": "my-dashboard"
}],
"includeReferences": true,
"createNewCopies": false,
"retries": {
"sales": [
{
Expand Down Expand Up @@ -246,6 +251,7 @@ $ curl -X POST api/spaces/_resolve_copy_saved_objects_errors
"id": "my-dashboard"
}],
"includeReferences": true,
"createNewCopies": false,
"retries": {
"marketing": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Increments all the specified counter fields by one. Creates the document if one
<b>Signature:</b>

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

## Parameters
Expand All @@ -23,7 +23,7 @@ incrementCounter(type: string, id: string, counterFieldNames: string[], options?

<b>Returns:</b>

`Promise<SavedObject>`
`Promise<SavedObject<T>>`

The saved object after the specified fields were incremented

Expand Down
24 changes: 24 additions & 0 deletions src/core/server/core_usage_data/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
*/

/** @internal */
export const CORE_USAGE_STATS_TYPE = 'core-usage-stats';

/** @internal */
export const CORE_USAGE_STATS_ID = 'core-usage-stats';
14 changes: 12 additions & 2 deletions src/core/server/core_usage_data/core_usage_data_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@
import { PublicMethodsOf } from '@kbn/utility-types';
import { BehaviorSubject } from 'rxjs';
import { CoreUsageDataService } from './core_usage_data_service';
import { CoreUsageData, CoreUsageDataStart } from './types';
import { coreUsageStatsClientMock } from './core_usage_stats_client.mock';
import { CoreUsageData, CoreUsageDataSetup, CoreUsageDataStart } from './types';

const createSetupContractMock = (usageStatsClient = coreUsageStatsClientMock.create()) => {
const setupContract: jest.Mocked<CoreUsageDataSetup> = {
registerType: jest.fn(),
getClient: jest.fn().mockReturnValue(usageStatsClient),
};
return setupContract;
};

const createStartContractMock = () => {
const startContract: jest.Mocked<CoreUsageDataStart> = {
Expand Down Expand Up @@ -140,7 +149,7 @@ const createStartContractMock = () => {

const createMock = () => {
const mocked: jest.Mocked<PublicMethodsOf<CoreUsageDataService>> = {
setup: jest.fn(),
setup: jest.fn().mockReturnValue(createSetupContractMock()),
start: jest.fn().mockReturnValue(createStartContractMock()),
stop: jest.fn(),
};
Expand All @@ -149,5 +158,6 @@ const createMock = () => {

export const coreUsageDataServiceMock = {
create: createMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};
68 changes: 65 additions & 3 deletions src/core/server/core_usage_data/core_usage_data_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.

import { CoreUsageDataService } from './core_usage_data_service';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { typeRegistryMock } from '../saved_objects/saved_objects_type_registry.mock';
import { CORE_USAGE_STATS_TYPE } from './constants';
import { CoreUsageStatsClient } from './core_usage_stats_client';

describe('CoreUsageDataService', () => {
const getTestScheduler = () =>
Expand Down Expand Up @@ -63,11 +66,67 @@ describe('CoreUsageDataService', () => {
service = new CoreUsageDataService(coreContext);
});

describe('setup', () => {
it('creates internal repository', async () => {
const metrics = metricsServiceMock.createInternalSetupContract();
const savedObjectsStartPromise = Promise.resolve(
savedObjectsServiceMock.createStartContract()
);
service.setup({ metrics, savedObjectsStartPromise });

const savedObjects = await savedObjectsStartPromise;
expect(savedObjects.createInternalRepository).toHaveBeenCalledTimes(1);
expect(savedObjects.createInternalRepository).toHaveBeenCalledWith([CORE_USAGE_STATS_TYPE]);
});

describe('#registerType', () => {
it('registers core usage stats type', async () => {
const metrics = metricsServiceMock.createInternalSetupContract();
const savedObjectsStartPromise = Promise.resolve(
savedObjectsServiceMock.createStartContract()
);
const coreUsageData = service.setup({
metrics,
savedObjectsStartPromise,
});
const typeRegistry = typeRegistryMock.create();

coreUsageData.registerType(typeRegistry);
expect(typeRegistry.registerType).toHaveBeenCalledTimes(1);
expect(typeRegistry.registerType).toHaveBeenCalledWith({
name: CORE_USAGE_STATS_TYPE,
hidden: true,
namespaceType: 'agnostic',
mappings: expect.anything(),
});
});
});

describe('#getClient', () => {
it('returns client', async () => {
const metrics = metricsServiceMock.createInternalSetupContract();
const savedObjectsStartPromise = Promise.resolve(
savedObjectsServiceMock.createStartContract()
);
const coreUsageData = service.setup({
metrics,
savedObjectsStartPromise,
});

const usageStatsClient = coreUsageData.getClient();
expect(usageStatsClient).toBeInstanceOf(CoreUsageStatsClient);
});
});
});

describe('start', () => {
describe('getCoreUsageData', () => {
it('returns core metrics for default config', () => {
it('returns core metrics for default config', async () => {
const metrics = metricsServiceMock.createInternalSetupContract();
service.setup({ metrics });
const savedObjectsStartPromise = Promise.resolve(
savedObjectsServiceMock.createStartContract()
);
service.setup({ metrics, savedObjectsStartPromise });
const elasticsearch = elasticsearchServiceMock.createStart();
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
body: [
Expand Down Expand Up @@ -243,8 +302,11 @@ describe('CoreUsageDataService', () => {
observables.push(newObservable);
return newObservable as Observable<any>;
});
const savedObjectsStartPromise = Promise.resolve(
savedObjectsServiceMock.createStartContract()
);

service.setup({ metrics });
service.setup({ metrics, savedObjectsStartPromise });

// Use the stopTimer$ to delay calling stop() until the third frame
const stopTimer$ = cold('---a|');
Expand Down
46 changes: 42 additions & 4 deletions src/core/server/core_usage_data/core_usage_data_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,29 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { CoreService } from 'src/core/types';
import { SavedObjectsServiceStart } from 'src/core/server';
import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server';
import { CoreContext } from '../core_context';
import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config';
import { HttpConfigType } from '../http';
import { LoggingConfigType } from '../logging';
import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config';
import { CoreServicesUsageData, CoreUsageData, CoreUsageDataStart } from './types';
import {
CoreServicesUsageData,
CoreUsageData,
CoreUsageDataStart,
CoreUsageDataSetup,
} from './types';
import { isConfigured } from './is_configured';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { KibanaConfigType } from '../kibana_config';
import { coreUsageStatsType } from './core_usage_stats';
import { CORE_USAGE_STATS_TYPE } from './constants';
import { CoreUsageStatsClient } from './core_usage_stats_client';
import { MetricsServiceSetup, OpsMetrics } from '..';

export interface SetupDeps {
metrics: MetricsServiceSetup;
savedObjectsStartPromise: Promise<SavedObjectsServiceStart>;
}

export interface StartDeps {
Expand All @@ -60,7 +69,8 @@ const kibanaOrTaskManagerIndex = (index: string, kibanaConfigIndex: string) => {
return index === kibanaConfigIndex ? '.kibana' : '.kibana_task_manager';
};

export class CoreUsageDataService implements CoreService<void, CoreUsageDataStart> {
export class CoreUsageDataService implements CoreService<CoreUsageDataSetup, CoreUsageDataStart> {
private logger: Logger;
private elasticsearchConfig?: ElasticsearchConfigType;
private configService: CoreContext['configService'];
private httpConfig?: HttpConfigType;
Expand All @@ -69,8 +79,10 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
private stop$: Subject<void>;
private opsMetrics?: OpsMetrics;
private kibanaConfig?: KibanaConfigType;
private coreUsageStatsClient?: CoreUsageStatsClient;

constructor(core: CoreContext) {
this.logger = core.logger.get('core-usage-stats-service');
this.configService = core.configService;
this.stop$ = new Subject();
}
Expand Down Expand Up @@ -130,8 +142,15 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
throw new Error('Unable to read config values. Ensure that setup() has completed.');
}

if (!this.coreUsageStatsClient) {
throw new Error(
'Core usage stats client is not initialized. Ensure that setup() has completed.'
);
}

const es = this.elasticsearchConfig;
const soUsageData = await this.getSavedObjectIndicesUsageData(savedObjects, elasticsearch);
const coreUsageStatsData = await this.coreUsageStatsClient.getUsageStats();

const http = this.httpConfig;
return {
Expand Down Expand Up @@ -225,10 +244,11 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
services: {
savedObjects: soUsageData,
},
...coreUsageStatsData,
};
}

setup({ metrics }: SetupDeps) {
setup({ metrics, savedObjectsStartPromise }: SetupDeps) {
metrics
.getOpsMetrics$()
.pipe(takeUntil(this.stop$))
Expand Down Expand Up @@ -268,6 +288,24 @@ export class CoreUsageDataService implements CoreService<void, CoreUsageDataStar
.subscribe((config) => {
this.kibanaConfig = config;
});

const internalRepositoryPromise = savedObjectsStartPromise.then((savedObjects) =>
savedObjects.createInternalRepository([CORE_USAGE_STATS_TYPE])
);

const registerType = (typeRegistry: SavedObjectTypeRegistry) => {
typeRegistry.registerType(coreUsageStatsType);
};

const getClient = () => {
const debugLogger = (message: string) => this.logger.debug(message);

return new CoreUsageStatsClient(debugLogger, internalRepositoryPromise);
};

this.coreUsageStatsClient = getClient();

return { registerType, getClient } as CoreUsageDataSetup;
}

start({ savedObjects, elasticsearch }: StartDeps) {
Expand Down
32 changes: 32 additions & 0 deletions src/core/server/core_usage_data/core_usage_stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { SavedObjectsType } from '../saved_objects';
import { CORE_USAGE_STATS_TYPE } from './constants';

/** @internal */
export const coreUsageStatsType: SavedObjectsType = {
name: CORE_USAGE_STATS_TYPE,
hidden: true,
namespaceType: 'agnostic',
mappings: {
dynamic: false, // we aren't querying or aggregating over this data, so we don't need to specify any fields
properties: {},
},
};
Loading